Add routes for mascara

feature/default_network_editable
Alexander Tseung 7 years ago
parent e226b10a89
commit dde39e82b5
  1. 77
      mascara/src/app/first-time/backup-phrase-screen.js
  2. 144
      mascara/src/app/first-time/create-password-screen.js
  3. 1
      mascara/src/app/first-time/index.css
  4. 87
      mascara/src/app/first-time/notice-screen.js
  5. 48
      ui/app/app.js
  6. 22
      ui/app/components/account-menu/index.js
  7. 16
      ui/app/components/pages/authenticated.js
  8. 5
      ui/app/components/pages/keychains/reveal-seed.js
  9. 28
      ui/app/components/pages/metamask-route.js
  10. 16
      ui/app/components/pages/notice.js
  11. 38
      ui/app/components/pages/unauthenticated/index.js
  12. 2
      ui/app/first-time/init-menu.js
  13. 4
      ui/app/routes.js

@ -1,5 +1,6 @@
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import classnames from 'classnames'
import shuffle from 'lodash.shuffle'
import {compose, onlyUpdateForPropTypes} from 'recompose'
@ -7,6 +8,7 @@ import Identicon from '../../../../ui/app/components/identicon'
import {confirmSeedWords} from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE } from '../../../../ui/app/routes'
const LockIcon = props => (
<svg
@ -35,27 +37,27 @@ const LockIcon = props => (
/>
</g>
</svg>
);
)
class BackupPhraseScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
address: PropTypes.string.isRequired,
seedWords: PropTypes.string.isRequired,
next: PropTypes.func.isRequired,
seedWords: PropTypes.string,
confirmSeedWords: PropTypes.func.isRequired,
history: PropTypes.object,
};
static defaultProps = {
seedWords: ''
};
seedWords: '',
}
static PAGE = {
SECRET: 'secret',
CONFIRM: 'confirm'
};
CONFIRM: 'confirm',
}
constructor(props) {
constructor (props) {
const {seedWords} = props
super(props)
this.state = {
@ -66,13 +68,20 @@ class BackupPhraseScreen extends Component {
}
}
componentWillMount () {
const { seedWords, history } = this.props
if (!seedWords) {
history.push(DEFAULT_ROUTE)
}
}
renderSecretWordsContainer () {
const { isShowingSecret } = this.state
return (
<div className="backup-phrase__secret">
<div className={classnames('backup-phrase__secret-words', {
'backup-phrase__secret-words--hidden': !isShowingSecret
'backup-phrase__secret-words--hidden': !isShowingSecret,
})}>
{this.props.seedWords}
</div>
@ -88,10 +97,10 @@ class BackupPhraseScreen extends Component {
</div>
)}
</div>
);
)
}
renderSecretScreen() {
renderSecretScreen () {
const { isShowingSecret } = this.state
return (
@ -109,7 +118,7 @@ class BackupPhraseScreen extends Component {
className="first-time-flow__button"
onClick={() => isShowingSecret && this.setState({
isShowingSecret: false,
page: BackupPhraseScreen.PAGE.CONFIRM
page: BackupPhraseScreen.PAGE.CONFIRM,
})}
disabled={!isShowingSecret}
>
@ -133,9 +142,9 @@ class BackupPhraseScreen extends Component {
)
}
renderConfirmationScreen() {
const { seedWords, confirmSeedWords, next } = this.props;
const { selectedSeeds, shuffledSeeds } = this.state;
renderConfirmationScreen () {
const { seedWords, confirmSeedWords, history } = this.props
const { selectedSeeds, shuffledSeeds } = this.state
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
return (
@ -165,17 +174,17 @@ class BackupPhraseScreen extends Component {
<button
key={i}
className={classnames('backup-phrase__confirm-seed-option', {
'backup-phrase__confirm-seed-option--selected': isSelected
'backup-phrase__confirm-seed-option--selected': isSelected,
})}
onClick={() => {
if (!isSelected) {
this.setState({
selectedSeeds: [...selectedSeeds, [i, word]]
selectedSeeds: [...selectedSeeds, [i, word]],
})
} else {
this.setState({
selectedSeeds: selectedSeeds
.filter(([index, seed]) => !(seed === word && index === i))
.filter(([index, seed]) => !(seed === word && index === i)),
})
}
}}
@ -187,7 +196,7 @@ class BackupPhraseScreen extends Component {
</div>
<button
className="first-time-flow__button"
onClick={() => isValid && confirmSeedWords().then(next)}
onClick={() => isValid && confirmSeedWords().then(() => history.push(DEFAULT_ROUTE))}
disabled={!isValid}
>
Confirm
@ -205,7 +214,7 @@ class BackupPhraseScreen extends Component {
onClick={e => {
e.preventDefault()
this.setState({
page: BackupPhraseScreen.PAGE.SECRET
page: BackupPhraseScreen.PAGE.SECRET,
})
}}
href="#"
@ -227,15 +236,21 @@ class BackupPhraseScreen extends Component {
}
render () {
return this.props.isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className="backup-phrase">
{this.renderBack()}
<Identicon address={this.props.address} diameter={70} />
{this.renderContent()}
</div>
)
return (
<div className="first-time-flow">
{
this.props.isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className="backup-phrase">
{this.renderBack()}
<Identicon address={this.props.address} diameter={70} />
{this.renderContent()}
</div>
)
}
</div>
)
}
}

@ -1,103 +1,109 @@
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux';
import {createNewVaultAndKeychain} from '../../../../ui/app/actions'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
import LoadingScreen from './loading-screen'
import Breadcrumbs from './breadcrumbs'
import { DEFAULT_ROUTE, IMPORT_ACCOUNT_ROUTE } from '../../../../ui/app/routes'
class CreatePasswordScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
createAccount: PropTypes.func.isRequired,
goToImportWithSeedPhrase: PropTypes.func.isRequired,
goToImportAccount: PropTypes.func.isRequired,
next: PropTypes.func.isRequired
history: PropTypes.object.isRequired,
}
state = {
password: '',
confirmPassword: ''
confirmPassword: '',
}
isValid() {
const {password, confirmPassword} = this.state;
isValid () {
const { password, confirmPassword } = this.state
if (!password || !confirmPassword) {
return false;
return false
}
if (password.length < 8) {
return false;
return false
}
return password === confirmPassword;
return password === confirmPassword
}
createAccount = () => {
if (!this.isValid()) {
return;
return
}
const {password} = this.state;
const {createAccount, next} = this.props;
const { password } = this.state
const { createAccount, history } = this.props
createAccount(password)
.then(next);
.then(() => history.push(DEFAULT_ROUTE))
}
render() {
const { isLoading, goToImportAccount, goToImportWithSeedPhrase } = this.props
render () {
const { isLoading } = this.props
return isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className="create-password">
<div className="create-password__title">
Create Password
</div>
<input
className="first-time-flow__input"
type="password"
placeholder="New Password (min 8 characters)"
onChange={e => this.setState({password: e.target.value})}
/>
<input
className="first-time-flow__input create-password__confirm-input"
type="password"
placeholder="Confirm Password"
onChange={e => this.setState({confirmPassword: e.target.value})}
/>
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.createAccount}
>
Create
</button>
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
goToImportWithSeedPhrase()
}}
>
Import with seed phrase
</a>
{ /* }
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
goToImportAccount()
}}
>
Import an account
</a>
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
</div>
)
return (
<div className="first-time-flow">
{
isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className="create-password">
<div className="create-password__title">
Create Password
</div>
<input
className="first-time-flow__input"
type="password"
placeholder="New Password (min 8 characters)"
onChange={e => this.setState({password: e.target.value})}
/>
<input
className="first-time-flow__input create-password__confirm-input"
type="password"
placeholder="Confirm Password"
onChange={e => this.setState({confirmPassword: e.target.value})}
/>
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.createAccount}
>
Create
</button>
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
history.push(IMPORT_ACCOUNT_ROUTE)
}}
>
Import with seed phrase
</a>
{ /* }
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
goToImportAccount()
}}
>
Import an account
</a>
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
</div>
)
}
</div>
)
}
}

@ -540,6 +540,7 @@ button.backup-phrase__confirm-seed-option:hover {
text-transform: uppercase;
margin: 35px 0 14px;
transition: 200ms ease-in-out;
background: #f7861c;
}
button.first-time-flow__button[disabled] {

@ -1,10 +1,12 @@
import React, {Component, PropTypes} from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Markdown from 'react-markdown'
import {connect} from 'react-redux'
import { connect } from 'react-redux'
import debounce from 'lodash.debounce'
import {markNoticeRead} from '../../../../ui/app/actions'
import { markNoticeRead } from '../../../../ui/app/actions'
import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs'
import { DEFAULT_ROUTE } from '../../../../ui/app/routes'
class NoticeScreen extends Component {
static propTypes = {
@ -12,70 +14,77 @@ class NoticeScreen extends Component {
lastUnreadNotice: PropTypes.shape({
title: PropTypes.string,
date: PropTypes.string,
body: PropTypes.string
body: PropTypes.string,
}),
next: PropTypes.func.isRequired
location: PropTypes.shape({
state: PropTypes.shape({
next: PropTypes.func.isRequired,
}),
}),
markNoticeRead: PropTypes.func,
history: PropTypes.object,
};
static defaultProps = {
lastUnreadNotice: {}
lastUnreadNotice: {},
};
state = {
atBottom: false,
}
componentDidMount() {
componentDidMount () {
this.onScroll()
}
acceptTerms = () => {
const { markNoticeRead, lastUnreadNotice, next } = this.props;
const defer = markNoticeRead(lastUnreadNotice)
.then(() => this.setState({ atBottom: false }))
if ((/terms/gi).test(lastUnreadNotice.title)) {
defer.then(next)
}
const { markNoticeRead, lastUnreadNotice, history } = this.props
markNoticeRead(lastUnreadNotice)
.then(() => {
history.push(DEFAULT_ROUTE)
this.setState({ atBottom: false })
})
}
onScroll = debounce(() => {
if (this.state.atBottom) return
const target = document.querySelector('.tou__body')
const {scrollTop, offsetHeight, scrollHeight} = target;
const atBottom = scrollTop + offsetHeight >= scrollHeight;
const {scrollTop, offsetHeight, scrollHeight} = target
const atBottom = scrollTop + offsetHeight >= scrollHeight
this.setState({atBottom: atBottom})
}, 25)
render() {
render () {
const {
address,
lastUnreadNotice: { title, body }
} = this.props;
lastUnreadNotice: { title, body },
} = this.props
const { atBottom } = this.state
return (
<div
className="tou"
onScroll={this.onScroll}
>
<Identicon address={address} diameter={70} />
<div className="tou__title">{title}</div>
<Markdown
className="tou__body markdown"
source={body}
skipHtml
/>
<button
className="first-time-flow__button"
onClick={atBottom && this.acceptTerms}
disabled={!atBottom}
<div className="first-time-flow">
<div
className="tou"
onScroll={this.onScroll}
>
Accept
</button>
<Breadcrumbs total={3} currentIndex={2} />
<Identicon address={address} diameter={70} />
<div className="tou__title">{title}</div>
<Markdown
className="tou__body markdown"
source={body}
skipHtml
/>
<button
className="first-time-flow__button"
onClick={atBottom && this.acceptTerms}
disabled={!atBottom}
>
Accept
</button>
<Breadcrumbs total={3} currentIndex={2} />
</div>
</div>
)
}
@ -84,9 +93,9 @@ class NoticeScreen extends Component {
export default connect(
({ metamask: { selectedAddress, lastUnreadNotice } }) => ({
lastUnreadNotice,
address: selectedAddress
address: selectedAddress,
}),
dispatch => ({
markNoticeRead: notice => dispatch(markNoticeRead(notice))
markNoticeRead: notice => dispatch(markNoticeRead(notice)),
})
)(NoticeScreen)

@ -5,8 +5,10 @@ const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
// mascara
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
const MascaraNoticeScreen = require('../../mascara/src/app/first-time/notice-screen').default
const MascaraBackupPhraseScreen = require('../../mascara/src/app/first-time/backup-phrase-screen').default
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
@ -22,6 +24,8 @@ const WalletView = require('./components/wallet-view')
// other views
const Authenticated = require('./components/pages/authenticated')
const Unauthenticated = require('./components/pages/unauthenticated')
const MetamaskRoute = require('./components/pages/metamask-route')
const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unauthenticated/unlock')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
@ -53,7 +57,7 @@ const {
IMPORT_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_MENU_ROUTE,
INITIALIZE_ROUTE,
NOTICE_ROUTE,
} = require('./routes')
@ -65,7 +69,7 @@ class App extends Component {
}
componentWillMount () {
const { currentCurrency, setCurrentCurrency } = this.props
const { currentCurrency, setCurrentCurrencyToUSD } = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
@ -77,14 +81,29 @@ class App extends Component {
return (
h(Switch, [
h(Route, { path: INITIALIZE_MENU_ROUTE, exact, component: InitializeMenuScreen }),
h(Route, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Route, { path: SETTINGS_ROUTE, component: Settings }),
h(Route, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Route, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(MetamaskRoute, {
path: INITIALIZE_ROUTE,
exact,
component: InitializeMenuScreen,
mascaraComponent: MascaraCreatePassword,
}),
h(MetamaskRoute, {
path: REVEAL_SEED_ROUTE,
exact,
component: RevealSeedPage,
mascaraComponent: MascaraBackupPhraseScreen,
}),
h(Unauthenticated, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Unauthenticated, { path: SETTINGS_ROUTE, component: Settings }),
h(Unauthenticated, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Unauthenticated, {
path: NOTICE_ROUTE,
exact,
component: NoticeScreen,
mascaraComponent: MascaraNoticeScreen,
}),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }),
@ -327,10 +346,17 @@ class App extends Component {
currentView,
activeAddress,
unapprovedTxs = {},
seedWords,
} = this.props
if (isMascara && isOnboarding) {
return h(MascaraFirstTime)
// seed words
if (seedWords) {
log.debug('rendering seed words')
return h(Redirect, {
to: {
pathname: REVEAL_SEED_ROUTE,
},
})
}
// notices

@ -8,7 +8,7 @@ const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const { SETTINGS_ROUTE, INFO_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../routes')
const { SETTINGS_ROUTE, INFO_ROUTE, IMPORT_ACCOUNT_ROUTE, DEFAULT_ROUTE } = require('../../routes')
module.exports = compose(
withRouter,
@ -40,22 +40,10 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.displayWarning(null))
dispatch(actions.toggleAccountMenu())
},
showConfigPage: () => {
dispatch(actions.showConfigPage())
dispatch(actions.toggleAccountMenu())
},
showNewAccountModal: () => {
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
dispatch(actions.toggleAccountMenu())
},
showImportPage: () => {
dispatch(actions.showImportPage())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.toggleAccountMenu())
},
}
}
@ -64,10 +52,7 @@ AccountMenu.prototype.render = function () {
isAccountMenuOpen,
toggleAccountMenu,
showNewAccountModal,
showImportPage,
lockMetamask,
showConfigPage,
showInfoPage,
history,
} = this.props
@ -78,7 +63,10 @@ AccountMenu.prototype.render = function () {
}, [
'My Accounts',
h('button.account-menu__logout-button', {
onClick: lockMetamask,
onClick: () => {
lockMetamask()
history.push(DEFAULT_ROUTE)
},
}, 'Log out'),
]),
h(Divider),

@ -1,26 +1,26 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Redirect, Route } = require('react-router-dom')
const { Redirect } = require('react-router-dom')
const h = require('react-hyperscript')
const { UNLOCK_ROUTE, INITIALIZE_MENU_ROUTE } = require('../../routes')
const MetamaskRoute = require('./metamask-route')
const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
const Authenticated = ({ component: Component, isUnlocked, isInitialized, ...props }) => {
const render = props => {
const component = renderProps => {
switch (true) {
case isUnlocked:
return h(Component, { ...props })
return h(Component, { ...renderProps })
case !isInitialized:
return h(Redirect, { to: { pathname: INITIALIZE_MENU_ROUTE } })
return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
default:
return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
}
}
return (
h(Route, {
h(MetamaskRoute, {
...props,
render,
component,
})
)
}

@ -8,7 +8,10 @@ const { DEFAULT_ROUTE } = require('../../../routes')
class RevealSeedPage extends Component {
componentDidMount () {
document.getElementById('password-box').focus()
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
checkConfirmation (event) {

@ -0,0 +1,28 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Route } = require('react-router-dom')
const h = require('react-hyperscript')
const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
return (
h(Route, {
...props,
component: isMascara && mascaraComponent ? mascaraComponent : component,
})
)
}
MetamaskRoute.propTypes = {
component: PropTypes.func,
mascaraComponent: PropTypes.func,
isMascara: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isMascara } } = state
return {
isMascara,
}
}
module.exports = connect(mapStateToProps)(MetamaskRoute)

@ -53,7 +53,6 @@ class Notice extends Component {
render () {
const { notice = {} } = this.props
const { title, date, body } = notice
// const state = this.state || { disclaimerDisabled: true }
const { disclaimerDisabled } = this.state
return (
@ -156,21 +155,6 @@ class Notice extends Component {
const mapStateToProps = state => {
const { metamask } = state
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
// if (!props.noActiveNotices) {
// log.debug('rendering notice screen for unread notices.')
// return h(NoticeScreen, {
// notice: props.lastUnreadNotice,
// key: 'NoticeScreen',
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
// })
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
// log.debug('rendering notice screen for lost accounts view.')
// return h(NoticeScreen, {
// notice: generateLostAccountsNotice(props.lostAccounts),
// key: 'LostAccountsNotice',
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
// })
// }
return {
noActiveNotices,

@ -0,0 +1,38 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Redirect } = require('react-router-dom')
const h = require('react-hyperscript')
const { INITIALIZE_ROUTE } = require('../../../routes')
const MetamaskRoute = require('../metamask-route')
const Unauthenticated = ({ component: Component, isInitialized, ...props }) => {
const component = renderProps => {
return isInitialized
? h(Component, { ...renderProps })
: h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
}
return (
h(MetamaskRoute, {
...props,
component,
})
)
}
Unauthenticated.propTypes = {
component: PropTypes.func,
isInitialized: PropTypes.bool,
isMascara: PropTypes.bool,
mascaraComponent: PropTypes.func,
}
const mapStateToProps = state => {
const { metamask: { isInitialized, isMascara } } = state
return {
isInitialized,
isMascara,
}
}
module.exports = connect(mapStateToProps)(Unauthenticated)

@ -35,7 +35,7 @@ class InitializeMenuScreen extends Component {
const { warning } = this.state
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('.initialize-screen.flex-column.flex-center', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,

@ -8,7 +8,7 @@ const ADD_TOKEN_ROUTE = '/add-token'
const IMPORT_ACCOUNT_ROUTE = '/import-account'
const SEND_ROUTE = '/send'
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
const INITIALIZE_MENU_ROUTE = '/initialize-menu'
const INITIALIZE_ROUTE = '/initialize'
const NOTICE_ROUTE = '/notice'
module.exports = {
@ -22,6 +22,6 @@ module.exports = {
IMPORT_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_MENU_ROUTE,
INITIALIZE_ROUTE,
NOTICE_ROUTE,
}

Loading…
Cancel
Save