diff --git a/src/features/rewards/index.ts b/src/features/rewards/index.ts
index 165575b79..ab8cf9659 100644
--- a/src/features/rewards/index.ts
+++ b/src/features/rewards/index.ts
@@ -27,8 +27,10 @@ import ModalBackupRestore from './modalBackupRestore'
import ModalContribute from './modalContribute'
import ModalDonation from './modalDonation'
import ModalPending from './modalPending'
+import ModalRedirect from './modalRedirect'
import NextContribution from './nextContribution'
import PanelWelcome from './panelWelcome'
+import PanelVerify from './panelVerify'
import Profile from './profile'
import RestoreSites from './restoreSites'
import RewardsButton from './rewardsButton'
@@ -47,6 +49,7 @@ import WalletEmpty from './walletEmpty'
import WalletOff from './walletOff'
import WalletPanel from './walletPanel'
import WalletPanelDisabled from './walletPanelDisabled'
+import WalletPopup from './walletPopup'
import WalletSummary from './walletSummary'
import WalletSummarySlider from './walletSummarySlider'
import WalletWrapper from './walletWrapper'
@@ -78,8 +81,10 @@ export {
ModalContribute,
ModalDonation,
ModalPending,
+ ModalRedirect,
NextContribution,
PanelWelcome,
+ PanelVerify,
Profile,
RestoreSites,
RewardsButton,
@@ -98,6 +103,7 @@ export {
WalletOff,
WalletPanel,
WalletPanelDisabled,
+ WalletPopup,
WalletSummary,
WalletSummarySlider,
WalletWrapper,
diff --git a/src/features/rewards/modalRedirect/__snapshots__/spec.tsx.snap b/src/features/rewards/modalRedirect/__snapshots__/spec.tsx.snap
new file mode 100644
index 000000000..c3b834f5f
--- /dev/null
+++ b/src/features/rewards/modalRedirect/__snapshots__/spec.tsx.snap
@@ -0,0 +1,105 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ModalRedirect tests basic tests matches the snapshot 1`] = `
+.c3 {
+ font-family: Poppins,sans-serif;
+}
+
+.c4 {
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 2;
+ color: #3b3e4f;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.c5 {
+ margin: 0 auto;
+ width: 30px;
+ height: 30px;
+}
+
+.c0 {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: rgba(12,13,33,0.85);
+ z-index: 99;
+ padding: 0 20px;
+ overflow: hidden;
+}
+
+.c1 {
+ max-width: 920px;
+ margin: 52px auto;
+ background: #fff;
+ border-radius: 6px;
+ overflow: hidden;
+ position: relative;
+}
+
+.c2 {
+ padding: 48px 48px;
+ overflow-y: auto;
+ max-height: calc(100vh - 100px);
+}
+
+.c6 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+
+
+
+
+
+ MISSING: processingRequest
+
+
+
+
+
+
+`;
diff --git a/src/features/rewards/modalRedirect/index.tsx b/src/features/rewards/modalRedirect/index.tsx
new file mode 100644
index 000000000..43abf5956
--- /dev/null
+++ b/src/features/rewards/modalRedirect/index.tsx
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import * as React from 'react'
+import {
+ StyledWrapper,
+ StyledTitle,
+ StyledLoader,
+ StyledError,
+ StyledButton
+} from './style'
+import Modal from '../../../components/popupModals/modal/index'
+import { getLocale } from '../../../helpers'
+import { LoaderIcon } from '../../../components/icons'
+import { Button } from '../../../components'
+
+export interface Props {
+ id?: string
+ errorText?: string
+ onClick?: () => void
+}
+
+export default class ModalRedirect extends React.PureComponent
{
+
+ getButton = () => {
+ const { onClick } = this.props
+ if (!onClick) {
+ return null
+ }
+
+ return (
+
+
+
+ )
+ }
+
+ render () {
+ const { id, errorText } = this.props
+
+ return (
+
+
+
+ {getLocale('processingRequest')}
+
+ {
+ errorText
+ ?
+ {errorText}
+ {this.getButton()}
+
+ :
+
+
+ }
+
+
+
+ )
+ }
+}
diff --git a/src/features/rewards/modalRedirect/spec.tsx b/src/features/rewards/modalRedirect/spec.tsx
new file mode 100644
index 000000000..4867100a2
--- /dev/null
+++ b/src/features/rewards/modalRedirect/spec.tsx
@@ -0,0 +1,24 @@
+/* global jest, expect, describe, it, afterEach */
+import * as React from 'react'
+import { shallow } from 'enzyme'
+import { create } from 'react-test-renderer'
+import ModalRedirect from './index'
+import { TestThemeProvider } from '../../../theme'
+
+describe('ModalRedirect tests', () => {
+ const baseComponent = (props?: object) =>
+
+ describe('basic tests', () => {
+ it('matches the snapshot', () => {
+ const component = baseComponent()
+ const tree = create(component).toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+
+ it('renders the component', () => {
+ const wrapper = shallow(baseComponent())
+ const assertion = wrapper.find('#modal').length
+ expect(assertion).toBe(1)
+ })
+ })
+})
diff --git a/src/features/rewards/modalRedirect/style.ts b/src/features/rewards/modalRedirect/style.ts
new file mode 100644
index 000000000..ff577f355
--- /dev/null
+++ b/src/features/rewards/modalRedirect/style.ts
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License. v. 2.0. If a copy of the MPL was not distributed with this file.
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import styled from 'styled-components'
+
+export const StyledWrapper = styled<{}, 'div'>('div')`
+ font-family: ${p => p.theme.fontFamily.heading};
+`
+
+export const StyledTitle = styled<{}, 'div'>('div')`
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 2;
+ color: ${p => p.theme.palette.grey800};
+ margin-bottom: 20px;
+ text-align: center;
+`
+
+export const StyledLoader = styled<{}, 'div'>('div')`
+ margin: 0 auto;
+ width: 30px;
+ height: 30px;
+`
+
+export const StyledError = styled<{}, 'div'>('div')`
+ text-align: center;
+`
+
+export const StyledButton = styled<{}, 'div'>('div')`
+ display: flex;
+ margin-top: 20px;
+ flex-direction: column;
+ align-items: center;
+`
diff --git a/src/features/rewards/panelVerify/__snapshots__/spec.tsx.snap b/src/features/rewards/panelVerify/__snapshots__/spec.tsx.snap
new file mode 100644
index 000000000..874388bda
--- /dev/null
+++ b/src/features/rewards/panelVerify/__snapshots__/spec.tsx.snap
@@ -0,0 +1,564 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PanelVerify tests basic tests matches the snapshot 1`] = `
+.c9 {
+ box-sizing: border-box;
+ font-family: Poppins,sans-serif;
+ font-weight: 400;
+ -webkit-font-smoothing: antialiased;
+ margin: 0;
+}
+
+.c8 {
+ font-size: 48px;
+}
+
+.c12 {
+ box-sizing: border-box;
+ font-family: Poppins,sans-serif;
+ font-weight: 400;
+ -webkit-font-smoothing: antialiased;
+ margin: 0;
+}
+
+.c11 {
+ font-size: 40px;
+}
+
+.c21 {
+ --button-main-color: #FB542B;
+ --button-main-color-hover: #FB542B;
+ --button-main-color-active: #ffb8a6;
+ --button-state-color: var(--button-main-color);
+ --icon-size: 18px;
+ --icon-spacing: 6px;
+ --webkit-appearance: none;
+ box-sizing: border-box;
+ background: none;
+ border: none;
+ outline-color: transparent;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row-reverse;
+ -ms-flex-direction: row-reverse;
+ flex-direction: row-reverse;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ font-family: Poppins,sans-serif;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ font-size: 14px;
+ border-radius: 28px;
+ width: 100%;
+ min-width: 235px;
+ padding: 19px 15px;
+}
+
+.c21:hover:enabled {
+ --button-state-color: var(--button-main-color-hover);
+}
+
+.c21:active:enabled {
+ --button-state-color: var(--button-main-color-active);
+}
+
+.c20 {
+ color: #fff;
+ background: var(--button-state-color);
+ border: 1px solid var(--button-state-color);
+}
+
+.c22 {
+ min-height: var(--icon-size);
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ text-align: center;
+ -webkit-letter-spacing: 0;
+ -moz-letter-spacing: 0;
+ -ms-letter-spacing: 0;
+ letter-spacing: 0;
+ font-weight: 500;
+ text-transform: uppercase;
+ line-height: 1;
+}
+
+.c0 {
+ position: absolute;
+ text-align: center;
+ font-family: Poppins,sans-serif;
+ background-image: linear-gradient(180deg,#4C54D2 0%,#563195 100%);
+ padding: 42px 42px 30px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 10;
+ height: 100%;
+ overflow-y: auto;
+}
+
+.c1 {
+ position: absolute;
+ top: 20px;
+ right: 20px;
+ cursor: pointer;
+ width: 11px;
+ height: 11px;
+ color: #FFFFFF;
+}
+
+.c3 {
+ margin-bottom: 42px;
+}
+
+.c4 {
+ display: inline-block;
+ vertical-align: middle;
+ width: 50px;
+ height: 50px;
+}
+
+.c6 {
+ width: calc(100% - 50px);
+ vertical-align: middle;
+ display: inline-block;
+ padding-left: 22px;
+}
+
+.c7 {
+ color: #FFFFFF;
+ text-align: left;
+ font-size: 22px;
+ line-height: 24px;
+ font-weight: 600;
+}
+
+.c10 {
+ color: #FFFFFF;
+ text-align: left;
+ font-family: Muli,sans-serif;
+ font-size: 14px;
+ line-height: 24px;
+}
+
+.c13 {
+ font-weight: 600;
+ font-size: 14px;
+ text-align: left;
+ line-height: 25px;
+ padding-bottom: 4px;
+ color: #FFFFFF;
+}
+
+.c14 {
+ color: #FFFFFF;
+ margin: 15px 0;
+}
+
+.c15 {
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ vertical-align: middle;
+}
+
+.c17 {
+ vertical-align: middle;
+ text-align: left;
+ font-family: Muli,sans-serif;
+ font-size: 15px;
+ line-height: 20px;
+ display: inline-block;
+ width: calc(100% - 30px);
+ padding-left: 12px;
+}
+
+.c18 {
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 600;
+ color: #FFFFFF;
+ padding: 30px 24px 20px;
+ border-top: 1px solid rgba(255,255,255,0.5433);
+ margin: 44px 0 0;
+}
+
+.c19 {
+ padding: 11px 15px;
+}
+
+.c23 {
+ font-family: Muli,sans-serif;
+ font-size: 12px;
+ color: #FFFFFF;
+ margin-top: 32px;
+}
+
+.c24 {
+ display: inline-block;
+ vertical-align: middle;
+ width: 20px;
+ height: 20px;
+}
+
+.c2 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+.c16 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+.c5 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+.c25 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MISSING: walletVerificationTitle1
+
+
+ MISSING: walletVerificationTitle2
+
+
+
+
+ MISSING: walletVerificationListHeader
+
+
+
+
+ MISSING: walletVerificationList1
+
+
+
+
+
+ MISSING: walletVerificationList2
+
+
+
+
+
+ MISSING: walletVerificationList3
+
+
+
+ MISSING: walletVerificationID
+
+
+
+ MISSING: walletVerificationButton
+
+
+
+ MISSING: walletVerificationFooter
+
+
+ Uphold
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/features/rewards/panelVerify/index.tsx b/src/features/rewards/panelVerify/index.tsx
new file mode 100644
index 000000000..49137962d
--- /dev/null
+++ b/src/features/rewards/panelVerify/index.tsx
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import * as React from 'react'
+import { getLocale } from '../../../helpers'
+
+import {
+ StyledWrapper,
+ StyledClose,
+ StyledHeader,
+ StyledBatIcon,
+ StyledHeaderText,
+ StyledTitle,
+ StyledSubtitle,
+ StyledListTitle,
+ StyledListItem,
+ StyledListIcon,
+ StyledListItemText,
+ StyledIDNotice,
+ StyledButton,
+ StyledFooter,
+ StyledFooterIcon
+} from './style'
+
+import {
+ CloseStrokeIcon,
+ UpholdColorIcon,
+ RewardsWalletCheck,
+ RewardsCheckIcon
+} from '../../../components/icons'
+
+export interface Props {
+ onVerifyClick: () => void
+ onClose: () => void
+ compact?: boolean
+ id?: string
+}
+
+export default class PanelWelcome extends React.PureComponent {
+ getListItem = (text: string, compact?: boolean) => (
+
+
+
+
+
+ {text}
+
+
+ )
+
+ getHeader = (onClose: () => void, compact?: boolean) => (
+ <>
+
+
+
+
+
+
+
+
+
+ {getLocale('walletVerificationTitle1')}
+
+
+ {getLocale('walletVerificationTitle2')}
+
+
+
+ >
+ )
+
+ getFooter = (compact?: boolean) => (
+
+ {getLocale('walletVerificationFooter')} Uphold
+
+
+
+
+ )
+
+ render () {
+ const {
+ onVerifyClick,
+ onClose,
+ compact,
+ id
+ } = this.props
+
+ return (
+
+ {this.getHeader(onClose, compact)}
+
+ {getLocale('walletVerificationListHeader')}
+
+ {this.getListItem(getLocale(compact ? 'walletVerificationListCompact1' : 'walletVerificationList1'), compact)}
+ {this.getListItem(getLocale(compact ? 'walletVerificationListCompact2' : 'walletVerificationList2'), compact)}
+ {this.getListItem(getLocale(compact ? 'walletVerificationListCompact3' : 'walletVerificationList3'), compact)}
+
+ {getLocale('walletVerificationID')}
+
+
+ {this.getFooter(compact)}
+
+ )
+ }
+}
diff --git a/src/features/rewards/panelVerify/spec.tsx b/src/features/rewards/panelVerify/spec.tsx
new file mode 100644
index 000000000..396eab30d
--- /dev/null
+++ b/src/features/rewards/panelVerify/spec.tsx
@@ -0,0 +1,24 @@
+/* global jest, expect, describe, it, afterEach */
+import * as React from 'react'
+import { shallow } from 'enzyme'
+import { create } from 'react-test-renderer'
+import PanelVerify from './index'
+import { TestThemeProvider } from '../../../theme'
+
+describe('PanelVerify tests', () => {
+ const baseComponent = (props?: object) =>
+
+ describe('basic tests', () => {
+ it('matches the snapshot', () => {
+ const component = baseComponent()
+ const tree = create(component).toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+
+ it('renders the component', () => {
+ const wrapper = shallow(baseComponent())
+ const assertion = wrapper.find('#panel-verify').length
+ expect(assertion).toBe(1)
+ })
+ })
+})
diff --git a/src/features/rewards/panelVerify/style.ts b/src/features/rewards/panelVerify/style.ts
new file mode 100644
index 000000000..c8694f193
--- /dev/null
+++ b/src/features/rewards/panelVerify/style.ts
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License. v. 2.0. If a copy of the MPL was not distributed with this file.
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import styled from 'styled-components'
+import Heading from '../../../components/text/heading'
+import Button, { Props as ButtonProps } from '../../../components/buttonsIndicators/button'
+import { ComponentType } from 'react'
+
+interface StyleProps {
+ compact?: boolean
+}
+
+export const StyledWrapper = styled<{}, 'div'>('div')`
+ position: absolute;
+ text-align: center;
+ font-family: ${p => p.theme.fontFamily.heading};
+ background-image: linear-gradient(180deg, ${p => p.theme.palette.blurple500} 0%, #563195 100%);
+ padding: 42px 42px 30px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 10;
+ height: 100%;
+ overflow-y: auto;
+`
+
+export const StyledClose = styled<{}, 'div'>('div')`
+ position: absolute;
+ top: 20px;
+ right: 20px;
+ cursor: pointer;
+ width: 11px;
+ height: 11px;
+ color: ${p => p.theme.palette.white};
+`
+
+export const StyledHeader = styled('div')`
+ margin-bottom: ${p => p.compact ? '15px' : '42px'};
+`
+
+export const StyledBatIcon = styled<{}, 'div'>('div')`
+ display: inline-block;
+ vertical-align: middle;
+ width: 50px;
+ height: 50px;
+`
+
+export const StyledHeaderText = styled<{}, 'div'>('div')`
+ width: calc(100% - 50px);
+ vertical-align: middle;
+ display: inline-block;
+ padding-left: 22px;
+`
+
+export const StyledTitle = styled(Heading)`
+ color: ${p => p.theme.palette.white};
+ text-align: left;
+ font-size: 22px;
+ line-height: 24px;
+ font-weight: 600;
+`
+
+export const StyledSubtitle = styled(Heading)`
+ color: ${p => p.theme.palette.white};
+ text-align: left;
+ font-family: ${p => p.theme.fontFamily.body};
+ font-size: 14px;
+ line-height: 24px;
+`
+
+export const StyledListTitle = styled(Heading)`
+ font-weight: 600;
+ font-size: 14px;
+ text-align: left;
+ line-height: 25px;
+ padding-bottom: 4px;
+ color: ${p => p.theme.palette.white};
+`
+
+export const StyledListItem = styled('div')`
+ color: ${p => p.theme.palette.white};
+ margin: ${p => p.compact ? '10px' : '15px'} 0;
+`
+
+export const StyledListIcon = styled<{}, 'div'>('div')`
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ vertical-align: middle;
+`
+
+export const StyledListItemText = styled<{}, 'div'>('div')`
+ vertical-align: middle;
+ text-align: left;
+ font-family: ${p => p.theme.fontFamily.body};
+ font-size: 15px;
+ line-height: 20px;
+ display: inline-block;
+ width: calc(100% - 30px);
+ padding-left: 12px;
+`
+
+export const StyledIDNotice = styled('div')`
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 600;
+ color: ${p => p.theme.palette.white};
+ padding: ${p => p.compact ? '20px' : '30px'} 24px 20px;
+ border-top: 1px solid rgba(255, 255, 255, 0.5433);
+ margin: ${p => p.compact ? '25px' : '44px'} 0 0;
+`
+
+export const StyledButton = styled(Button as ComponentType)`
+ padding: 11px 15px;
+`
+
+export const StyledFooter = styled('div')`
+font-family: ${p => p.theme.fontFamily.body};
+ font-size: 12px;
+ color: ${p => p.theme.palette.white};
+ margin-top: ${p => p.compact ? '28px' : '32px'};
+`
+
+export const StyledFooterIcon = styled<{}, 'div'>('div')`
+ display: inline-block;
+ vertical-align: middle;
+ width: 20px;
+ height: 20px;
+`
diff --git a/src/features/rewards/walletPopup/__snapshots__/spec.tsx.snap b/src/features/rewards/walletPopup/__snapshots__/spec.tsx.snap
new file mode 100644
index 000000000..c06fcc932
--- /dev/null
+++ b/src/features/rewards/walletPopup/__snapshots__/spec.tsx.snap
@@ -0,0 +1,168 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WalletSummary tests basic tests matches the snapshot 1`] = `
+.c0 {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: rgba(0,0,0,0);
+ z-index: 99;
+ padding: 0 20px;
+ overflow: hidden;
+}
+
+.c1 {
+ margin: 52px auto;
+ background: #FFFFFF;
+ border-radius: 6px;
+ overflow: hidden;
+ position: relative;
+ box-shadow: 0 0 12px 0 rgba(12,13,33,0.44);
+}
+
+.c2 {
+ padding: 20px;
+}
+
+.c3 {
+ font-weight: bold;
+ border-bottom: 1px solid #c2c4cf;
+ padding-bottom: 5px;
+ margin-bottom: 10px;
+}
+
+.c6 {
+ font-weight: normal;
+ float: right;
+ color: #39a84c;
+}
+
+.c4 {
+ vertical-align: middle;
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ margin-left: -5px;
+}
+
+.c5 {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+
+
+`;
diff --git a/src/features/rewards/walletPopup/index.tsx b/src/features/rewards/walletPopup/index.tsx
new file mode 100644
index 000000000..9f382d7c9
--- /dev/null
+++ b/src/features/rewards/walletPopup/index.tsx
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import * as React from 'react'
+
+// Helpers
+import { getLocale } from '../../../helpers'
+
+// Styled Components
+import {
+ StyledContent,
+ StyledDialog,
+ StyledHeader,
+ StyledIcon,
+ StyledStatus,
+ StyledWrapper
+} from './style'
+import {
+ UpholdColorIcon
+} from '../../../components/icons'
+
+export interface Props {
+ children: React.ReactNode
+ onClose: () => void
+ userName: string
+ id?: string
+}
+
+export default class WalletPopup extends React.PureComponent {
+ insideClick = (e: React.SyntheticEvent) => {
+ // Don't propogate click to container, which will close it
+ e.stopPropagation()
+ }
+
+ render () {
+ const {
+ children,
+ onClose,
+ userName,
+ id
+ } = this.props
+ return (
+
+
+
+
+
+
+
+ {userName}
+
+ {getLocale('walletVerified')}
+
+
+ {children}
+
+
+
+ )
+ }
+}
diff --git a/src/features/rewards/walletPopup/spec.tsx b/src/features/rewards/walletPopup/spec.tsx
new file mode 100644
index 000000000..42d5a1025
--- /dev/null
+++ b/src/features/rewards/walletPopup/spec.tsx
@@ -0,0 +1,34 @@
+/* global jest, expect, describe, it, afterEach */
+import * as React from 'react'
+import { shallow } from 'enzyme'
+import { create } from 'react-test-renderer'
+import WalletPopup, { Props } from './index'
+import { TestThemeProvider } from '../../../theme'
+
+const props = {
+ onClose: () => null,
+ userName: 'tester',
+}
+
+describe('WalletSummary tests', () => {
+ const baseComponent = (props: Props) =>
+
+
+
+
+ describe('basic tests', () => {
+ it('matches the snapshot', () => {
+ const component = baseComponent(props)
+ const tree = create(component).toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+
+ it('renders the component', () => {
+ const wrapper = shallow(baseComponent(props))
+ const assertion = wrapper.find('#wallet-popup').length
+ expect(assertion).toBe(1)
+ })
+ })
+})
diff --git a/src/features/rewards/walletPopup/style.ts b/src/features/rewards/walletPopup/style.ts
new file mode 100644
index 000000000..5a06abb68
--- /dev/null
+++ b/src/features/rewards/walletPopup/style.ts
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License. v. 2.0. If a copy of the MPL was not distributed with this file.
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import styled from 'styled-components'
+
+export const StyledWrapper = styled<{}, 'div'>('div')`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0);
+ z-index: 99;
+ padding: 0 20px;
+ overflow: hidden;
+`
+
+export const StyledDialog = styled<{}, 'div'>('div')`
+ margin: 52px auto;
+ background: ${p => p.theme.palette.white};
+ border-radius: 6px;
+ overflow: hidden;
+ position: relative;
+ box-shadow: 0 0 12px 0 rgba(12, 13, 33, 0.44);
+`
+
+export const StyledContent = styled<{}, 'div'>('div')`
+ padding: 20px;
+`
+
+export const StyledHeader = styled<{}, 'div'>('div')`
+ font-weight: bold;
+ border-bottom: 1px solid ${p => p.theme.palette.grey400};
+ padding-bottom: 5px;
+ margin-bottom: 10px;
+`
+
+export const StyledStatus = styled<{}, 'div'>('div')`
+ font-weight: normal;
+ float: right;
+ color: ${p => p.theme.palette.green600};
+`
+
+export const StyledIcon = styled<{}, 'span'>('span')`
+ vertical-align: middle;
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ margin-left: -5px;
+`
diff --git a/src/features/rewards/walletSummarySlider/style.ts b/src/features/rewards/walletSummarySlider/style.ts
index 06ad67f2b..85e96b348 100644
--- a/src/features/rewards/walletSummarySlider/style.ts
+++ b/src/features/rewards/walletSummarySlider/style.ts
@@ -19,6 +19,7 @@ export const StyledTransitionWrapper = styled('div')`
height: ${p => p.show ? '100%' : '0'};
opacity: ${p => p.show ? '1' : '0'};
overflow: ${p => p.show ? 'unset' : 'hidden'};
+ position: relative;
` as any
export const StyledToggleWrapper = styled('div')`
diff --git a/src/features/rewards/walletWrapper/__snapshots__/spec.tsx.snap b/src/features/rewards/walletWrapper/__snapshots__/spec.tsx.snap
index 72efd098e..fab507e47 100644
--- a/src/features/rewards/walletWrapper/__snapshots__/spec.tsx.snap
+++ b/src/features/rewards/walletWrapper/__snapshots__/spec.tsx.snap
@@ -18,11 +18,13 @@ exports[`WalletWrapper tests basic tests matches the snapshot 1`] = `
flex-direction: column;
max-width: 415px;
margin: 0 auto;
+ position: relative;
}
.c1 {
padding: 16px 21px 0 19px;
position: relative;
+ z-index: 2;
}
.c2 {
@@ -54,7 +56,6 @@ exports[`WalletWrapper tests basic tests matches the snapshot 1`] = `
.c8 {
padding: 0px;
- position: relative;
background: #fff;
-webkit-flex: 1;
-ms-flex: 1;
diff --git a/src/features/rewards/walletWrapper/index.tsx b/src/features/rewards/walletWrapper/index.tsx
index 7b44adedc..b97d8b2b2 100644
--- a/src/features/rewards/walletWrapper/index.tsx
+++ b/src/features/rewards/walletWrapper/index.tsx
@@ -16,6 +16,7 @@ import {
StyledCopyImage,
StyledIconAction,
StyledBalanceConverted,
+ StyledBalanceUnavailable,
StyledGrantWrapper,
StyledGrant,
StyledActionWrapper,
@@ -35,12 +36,19 @@ import {
StyledButtonWrapper,
StyledButton,
StyledNotificationMessage,
- StyledPipe
+ StyledPipe,
+ StyledWalletButton,
+ StyledVerifiedButton,
+ StyledVerifiedButtonIcon,
+ StyledVerifiedButtonText,
+ StyledTextIcon,
+ StyledDialogList,
+ StyledLink
} from './style'
import { getLocale } from '../../../helpers'
-import { GrantCaptcha, GrantComplete, GrantError, GrantWrapper } from '../'
+import { GrantCaptcha, GrantComplete, GrantError, GrantWrapper, WalletPopup } from '../'
import Alert, { Type as AlertType } from '../alert'
-import { Button } from '../../../components'
+import Button, { Props as ButtonProps } from '../../../components/buttonsIndicators/button'
import {
CaratDownIcon,
CaratUpIcon,
@@ -96,8 +104,15 @@ export type NotificationType =
'tipsProcessed' |
'error' |
'pendingContribution' |
+ 'verifyWallet' |
''
+export type WalletState =
+ 'unverified' |
+ 'verified' |
+ 'disconnected_unverified' |
+ 'disconnected_verified'
+
export interface Notification {
id: string
date?: string
@@ -110,7 +125,7 @@ export interface Props {
balance: string
converted: string | null
actions: ActionWallet[]
- connectedWallet?: boolean
+ walletState?: WalletState
compact?: boolean
contentPadding?: boolean
showCopy?: boolean
@@ -130,19 +145,25 @@ export interface Props {
onFinish?: () => void
onSolution?: (x: number, y: number) => void
convertProbiToFixed?: (probi: string, place: number) => string
+ onVerifyClick?: () => void
+ onDisconnectClick?: () => void
+ goToUphold?: () => void
+ userName?: string
}
export type Step = '' | 'captcha' | 'complete'
interface State {
- grantDetails: boolean
+ grantDetails: boolean,
+ verificationDetails: boolean
}
export default class WalletWrapper extends React.PureComponent {
constructor (props: Props) {
super(props)
this.state = {
- grantDetails: false
+ grantDetails: false,
+ verificationDetails: false
}
}
@@ -281,6 +302,10 @@ export default class WalletWrapper extends React.PureComponent {
buttonText = getLocale('addFunds')
buttonAction = this.onNotificationClick
break
+ case 'verifyWallet':
+ buttonText = getLocale('whyHow').toUpperCase()
+ buttonAction = this.onNotificationClick
+ break
default:
buttonText = getLocale('ok').toUpperCase()
break
@@ -321,6 +346,102 @@ export default class WalletWrapper extends React.PureComponent {
)
}
+ generateWalletButton = (walletState: WalletState) => {
+ const buttonProps: Partial = {
+ size: 'small',
+ level: 'primary',
+ brand: 'rewards',
+ onClick: this.props.onVerifyClick
+ }
+
+ switch (walletState) {
+ case 'unverified':
+ return (
+ !,
+ position: 'after'
+ }}
+ text={getLocale('walletButtonUnverified')}
+ {...buttonProps}
+ id={'verify-wallet-button'}
+ />
+ )
+
+ case 'verified':
+ return (
+
+
+
+
+
+ {getLocale('walletButtonVerified')}
+
+
+
+
+
+ )
+
+ case 'disconnected_unverified':
+ case 'disconnected_verified':
+ return (
+ ,
+ position: 'before'
+ }}
+ {...buttonProps}
+ id={'disconnected-wallet-button'}
+ />
+ )
+ }
+ }
+
+ toggleVerificationDetails = () => {
+ this.setState({ verificationDetails: !this.state.verificationDetails })
+ }
+
+ onDetailsLinkClicked = (action?: () => void) => {
+ if (action) {
+ action()
+ }
+ this.toggleVerificationDetails()
+ }
+
+ getVerificationDetails = () => {
+ const { goToUphold, userName, onDisconnectClick } = this.props
+
+ return (
+
+ {
+
+
+
+ {getLocale('walletGoToUphold')}
+
+
+
+
+ {getLocale('walletDisconnect')}
+
+
+
+ }
+
+ )
+ }
+
toggleGrantDetails = () => {
this.setState({ grantDetails: !this.state.grantDetails })
}
@@ -336,6 +457,8 @@ export default class WalletWrapper extends React.PureComponent {
case 'ads':
case 'ads-launch':
case 'backupWallet':
+ case 'insufficientFunds':
+ case 'verifyWallet':
icon = megaphoneIconUrl
break
case 'contribute':
@@ -346,9 +469,6 @@ export default class WalletWrapper extends React.PureComponent {
case 'grant':
icon = giftIconUrl
break
- case 'insufficientFunds':
- icon = megaphoneIconUrl
- break
default:
icon = ''
break
@@ -391,6 +511,9 @@ export default class WalletWrapper extends React.PureComponent {
case 'pendingContribution':
typeText = getLocale('pendingContributionTitle')
break
+ case 'verifyWallet':
+ typeText = getLocale('verifyWalletTitle')
+ break
default:
typeText = ''
break
@@ -417,7 +540,7 @@ export default class WalletWrapper extends React.PureComponent {
converted,
actions,
showCopy,
- connectedWallet,
+ walletState,
compact,
contentPadding,
showSecActions,
@@ -428,7 +551,9 @@ export default class WalletWrapper extends React.PureComponent {
gradientTop,
notification,
isMobile,
- convertProbiToFixed
+ convertProbiToFixed,
+ onVerifyClick,
+ onDisconnectClick
} = this.props
const hasGrants = this.hasGrants(grants)
@@ -443,6 +568,9 @@ export default class WalletWrapper extends React.PureComponent {
date = new Date(grant.expiryTime).toLocaleDateString()
}
+ const walletVerified = walletState === 'verified' || walletState === 'disconnected_verified'
+ const connectedVerified = walletState === 'verified'
+
return (
<>
{
{
!notification
? <>
+ {
+ this.state.verificationDetails && connectedVerified && onDisconnectClick
+ ? this.getVerificationDetails()
+ : null
+ }
{
alert && alert.node
?
@@ -488,7 +621,11 @@ export default class WalletWrapper extends React.PureComponent {
: null
}
- {getLocale('yourWallet')}
+ {
+ walletState
+ ? this.generateWalletButton(walletState)
+ : {getLocale('yourWallet')}
+ }
{
showSecActions
?
@@ -496,30 +633,45 @@ export default class WalletWrapper extends React.PureComponent {
: null
}
-
-
- {balance} BAT
-
- {
- converted
- ? {converted}
- : null
- }
- {
- hasGrants
- ?
+ {
+ walletState && (walletState === 'disconnected_unverified' || walletState === 'disconnected_verified')
+ ?
+ {getLocale('balanceUnavailable')}
+
: }}
+ onClick={onVerifyClick}
/>
- : null
- }
-
+
+ :
+
+ {balance} BAT
+
+ {
+ converted
+ ? {converted}
+ : null
+ }
+ {
+ hasGrants
+ ?
+ : }}
+ />
+
+ : null
+ }
+
+ }
{
this.state.grantDetails && hasGrants
?
@@ -553,21 +705,32 @@ export default class WalletWrapper extends React.PureComponent {
{
showCopy
- ?
+ ?
{
- connectedWallet
+ walletVerified
? <>
{getLocale('rewardsPanelText1')} Uphold .
- >
+ >
: <>
{getLocale('rewardsPanelText2')} Uphold .
- >
+ {
+ onVerifyClick
+ ? <>
+ {' ('}
+
+ {getLocale('rewardsPanelTextVerify')}
+
+ {')'}
+ >
+ : null
+ }
+ >
}
: null
diff --git a/src/features/rewards/walletWrapper/style.ts b/src/features/rewards/walletWrapper/style.ts
index 61685eafd..b8433edc0 100644
--- a/src/features/rewards/walletWrapper/style.ts
+++ b/src/features/rewards/walletWrapper/style.ts
@@ -4,6 +4,7 @@
import { Notification } from './'
import styled from '../../../theme'
+import palette from '../../../theme/colors'
import Button, { Props as ButtonProps } from '../../../components/buttonsIndicators/button'
import { ComponentType } from 'react'
@@ -44,11 +45,13 @@ export const StyledWrapper = styled('div')`
flex-direction: column;
max-width: 415px;
margin: 0 auto;
+ position: relative;
`
export const StyledHeader = styled<{}, 'div'>('div')`
padding: 16px 21px 0 19px;
position: relative;
+ z-index: 2;
`
export const StyledTitle = styled<{}, 'div'>('div')`
@@ -77,9 +80,16 @@ export const StyledBalanceTokens = styled<{}, 'div'>('div')`
font-weight: 300;
`
+export const StyledBalanceUnavailable = styled<{}, 'div'>('div')`
+ font-size: 24px;
+ opacity: 0.66;
+ color: ${p => p.theme.palette.white};
+ margin: 10px 0;
+ font-weight: 300;
+`
+
export const StyledContent = styled('div')`
padding: ${p => p.contentPadding ? '11px 25px 19px' : '0px'};
- position: relative;
background: #fff;
flex: 1;
`
@@ -112,7 +122,7 @@ export const StyledCopy = styled('div')`
font-size: 12px;
color: #838391;
padding: 19px 15px;
- background: ${p => p.connected ? '#dcdfff' : '#dee2e6'};
+ background: ${p => p.connected ? '#c4f2db' : '#dee2e6'};
text-align: center;
`
@@ -314,3 +324,72 @@ export const StyledButton = styled(Button as ComponentType)`
export const StyledPipe = styled('span')`
font-weight: 300;
`
+
+export const StyledWalletButton = styled(Button as ComponentType)`
+ padding-right: 23px;
+`
+
+export const StyledVerifiedButton = styled<{active: boolean}, 'button'>('button')`
+ box-sizing: border-box;
+ outline-color: transparent;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ font-family: Poppins, sans-serif;
+ cursor: pointer;
+ user-select: none;
+ font-size: 11px;
+ border-radius: 28px;
+ min-width: 88px;
+ padding: 7px 10px;
+ color: #fff;
+ background: ${palette.green600};
+ border: 1px solid ${p => p.theme.color[p.active ? 'brandBatActive' : 'brandBat']};
+ :active:enabled {
+ border-color: ${p => p.theme.color.brandBatActive};
+ }
+`
+
+export const StyledVerifiedButtonText = styled<{}, 'div'>('div')`
+ /* min-height so that we get consistent height with / without an icon */
+ min-height: 14px;
+ display: flex;
+ align-items: center;
+ text-align: center;
+ letter-spacing: 0;
+ font-weight: 500;
+ line-height: 1;
+`
+
+export const StyledVerifiedButtonIcon = styled<{position: string}, 'div'>('div')`
+ display: block;
+ line-height: 0;
+ height: 14px;
+ width: 14px;
+ margin: ${(p) => p.position === 'before' ? '0 6px 0 -4px' : '0 -4px 0 6px'};
+`
+
+export const StyledTextIcon = styled<{}, 'div'>('div')`
+ line-height: initial;
+ background: ${palette.blurple600};
+ width: 16px;
+ height: 16px;
+ border-radius: 8px;
+ margin: 0 10px;
+`
+
+export const StyledDialogList = styled<{}, 'ul'>('ul')`
+ list-style-position: inside;
+ padding-left: 0;
+ margin: 0;
+ line-height: 150%;
+`
+
+export const StyledLink = styled<{}, 'a'>('a')`
+ color: ${palette.blue400};
+ font-weight: bold;
+ text-decoration: none;
+ display: inline-block;
+ cursor: pointer;
+`
diff --git a/stories/assets/locale.ts b/stories/assets/locale.ts
index a62aafd28..24a9c9f5d 100644
--- a/stories/assets/locale.ts
+++ b/stories/assets/locale.ts
@@ -22,6 +22,7 @@ const locale: Record = {
backup: 'Backup',
backupNow: 'Backup Now',
backupWalletTitle: 'Backup Wallet',
+ balanceUnavailable: 'Unavailable',
bat: 'BAT',
braveAdsDesc: 'No action required. Just collect tokens. Your data is safe with our Shields.',
braveAdsTitle: 'Brave Ads',
@@ -138,9 +139,12 @@ const locale: Record = {
pleaseNote: 'Please note:',
print: 'Print',
privacyPolicy: 'Privacy Policy',
+ processingRequest: 'You will be redirected shorty to verify your wallet.',
+ processingRequestButton: 'Try again',
readyToTakePart: 'Ready to get started?',
readyToTakePartOptInText: 'Yes I\'m Ready!',
readyToTakePartStart: 'You can start with the',
+ reconnectWallet: 'Reconnect wallet',
recoveryKeys: 'Recovery Key',
recurring: 'Recurring',
recurringDonation: 'Recurring Donation',
@@ -177,9 +181,10 @@ const locale: Record = {
rewardsPanelOffText1: 'Get Rewarded for Browsing!',
rewardsPanelOffText2: 'Earn tokens for your attention to ads and pay it forward to support content creators you value!',
rewardsPanelText1: 'Add, withdraw and manage funds at',
- rewardsPanelText2: 'Brave wallet is managed by',
+ rewardsPanelText2: 'Your Brave wallet is powered by',
rewardsPanelText3: 'Brave Rewards is built on the Basic Attention Token. Learn more about BAT',
rewardsPanelText4: 'here',
+ rewardsPanelTextVerify: 'verify',
rewardsRestoreText1: 'Restore your Wallet',
rewardsRestoreText2: 'Use your recovery key to restore your wallet.',
rewardsRestoreText3: 'Restoring with a recovery key will replace your current wallet. So make sure you empty or back up your current wallet before restoring.',
@@ -228,14 +233,33 @@ const locale: Record = {
unVerifiedText: 'This creator has not yet signed up to receive contributions from Brave users.',
unVerifiedTextMore: 'Learn more.',
verifiedPublisher: 'Verified Creator',
+ verifyWalletTitle: 'Verify your wallet',
viewDetails: 'View Details',
viewMonthly: 'View Monthly Statement for Details',
walletActivity: 'Wallet Activity',
walletAddress: 'Wallet Address',
walletBalance: 'wallet balance',
+ walletButtonDisconnected: 'Disconnected',
+ walletButtonUnverified: 'Verify Wallet',
+ walletButtonVerified: 'Verified',
walletFailedButton: 'Re-try',
walletFailedTitle: 'Wallet creation failed',
walletFailedText: 'Please check your internet connection.',
+ walletGoToUphold: 'Go to my Uphold account',
+ walletDisconnect: 'Disconnect from Brave Rewards',
+ walletVerificationButton: 'Verify wallet',
+ walletVerificationFooter: 'Our wallet service is provided by',
+ walletVerificationID: 'Please have your government issued ID available.',
+ walletVerificationList1: 'Accept token rewards without a CAPTCHA',
+ walletVerificationList2: 'Purchase more tokens with your credit card',
+ walletVerificationList3: 'Withdraw tokens you purchased',
+ walletVerificationListCompact1: 'Keep your Ad Rewards tokens',
+ walletVerificationListCompact2: 'Add tokens using credit card',
+ walletVerificationListCompact3: 'Withdraw tokens',
+ walletVerificationListHeader: 'When you verify your wallet, you can:',
+ walletVerificationTitle1: 'Verify Your Wallet',
+ walletVerificationTitle2: 'And manage your funds easily!',
+ walletVerified: 'Verified',
welcome: 'Welcome!',
welcomeBack: 'Welcome Back!',
welcomeButtonTextOne: 'Start Earning Now!',
@@ -249,6 +273,7 @@ const locale: Record = {
whyBraveRewards: 'Why Brave Rewards?',
whyBraveRewardsDesc1: 'With conventional browsers, you pay to browse the web by viewing ads with your valuable attention, spending your valuable time downloading invasive ad technology, that transmits your valuable private data to advertisers — without your consent.',
whyBraveRewardsDesc2: 'Well, you\'ve come to the right place. Brave welcomes you to the new internet. One where your time is valued, your personal data is kept private, and you actually get paid for your attention.',
+ whyHow: 'Why & How',
yourWallet: 'Your wallet'
}
diff --git a/stories/features/rewards/concepts.tsx b/stories/features/rewards/concepts.tsx
index 633f1357b..b7d91b4cd 100644
--- a/stories/features/rewards/concepts.tsx
+++ b/stories/features/rewards/concepts.tsx
@@ -24,7 +24,7 @@ import {
} from '../../../src/features/rewards'
import { BatColorIcon, WalletAddIcon } from '../../../src/components/icons'
import WelcomePage from '../../../src/features/rewards/welcomePage'
-import { Notification } from '../../../src/features/rewards/walletWrapper'
+import { Notification, WalletState } from '../../../src/features/rewards/walletWrapper'
const favicon = require('../../assets/img/brave-favicon.png')
const siteBgImage = require('../../assets/img/bg_siteBanner.jpg')
@@ -71,7 +71,39 @@ const dummyOptInAction = () => {
storiesOf('Feature Components/Rewards/Concepts/Desktop', module)
.addDecorator(withKnobs)
- .add('Settings Page', () => )
+ .add('Settings Page', () => {
+ const walletProps = {
+ grants: object('Claimed grants', [
+ {
+ tokens: '8.0',
+ expireDate: '7/15/2018',
+ type: 'ugp'
+ },
+ {
+ tokens: '10.0',
+ expireDate: '9/10/2018',
+ type: 'ugp'
+ },
+ {
+ tokens: '10.0',
+ expireDate: '10/10/2018',
+ type: 'ads'
+ }
+ ]),
+ content: select('Content', {
+ empty: 'empty',
+ summary: 'summary',
+ off: 'off'
+ }, 'empty') as 'empty' | 'summary' | 'off',
+ walletState: select('wallet status', {
+ unverified: 'unverified',
+ verified: 'verified',
+ 'disconnected unverified': 'disconnected_unverified',
+ 'disconnected verified': 'disconnected_verified'
+ }, 'unverified') as WalletState
+ }
+ return ( )
+ })
.add('Welcome Page', () => (
console.log('onVerifyClick')
+ const onDisconnectClick = () => console.log('onDisconnectClick')
+
const convertProbiToFixed = (probi: string, places: number = 1) => {
return '0.0'
}
@@ -402,7 +436,8 @@ storiesOf('Feature Components/Rewards/Concepts/Desktop', module)
)
})
+ .add('Redirect',() => {
+ return (
+
+ )
+ })
diff --git a/stories/features/rewards/other.tsx b/stories/features/rewards/other.tsx
index e96d808ee..e46064639 100644
--- a/stories/features/rewards/other.tsx
+++ b/stories/features/rewards/other.tsx
@@ -20,6 +20,7 @@ import {
Profile,
Amount,
PanelWelcome,
+ PanelVerify,
ToggleTips,
Tooltip,
DonationOverlay,
@@ -224,6 +225,25 @@ storiesOf('Feature Components/Rewards/Other/Desktop', module)
)
})
+ .add('Panel Verification', () => {
+ const onVerifyClick = () =>
+ console.log('onVerifyClick')
+
+ const onClose = () =>
+ console.log('onClose')
+
+ const compact = boolean('compact panel', false)
+
+ return (
+