RegisterV2.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. /**
  2. * 注册页面V2
  3. * @邠心vbe on 2022/12/23
  4. */
  5. import React from 'react';
  6. import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
  7. import { BackIcon } from '../../components/Toolbar';
  8. import apiUser from '../../api/apiUser';
  9. import Button from '../../components/Button';
  10. import { PageList } from '../Router';
  11. import Dialog from '../../components/Dialog';
  12. import Modal from 'react-native-modal';
  13. import { RegisterDialog } from '../charge/InfoDialog';
  14. import Dropdown from '../../components/Dropdown';
  15. import ImagePicker from 'react-native-image-crop-picker';
  16. import apiUpload from '../../api/apiUpload';
  17. import { host } from '../../api/http';
  18. import { ModalProps } from '../../components/BottomModal';
  19. import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
  20. import CheckBox from '../../components/CheckBox';
  21. const options = {
  22. width: 300,
  23. height: 200,
  24. cropping: true,
  25. multiple: false,
  26. mediaType: 'photo',
  27. writeTempFile: false,
  28. compressImageQuality: 0.8,
  29. compressImageMaxWidth: 720,
  30. compressImageMaxHeight: 1280,
  31. cropperStatusBarColor: colorAccent,
  32. cropperToolbarColor: colorAccent,
  33. cropperActiveWidgetColor: colorAccent
  34. }
  35. export const StrengthView = ({strength}) => {
  36. const getStyle = (num) => {
  37. if (strength >= num) {
  38. return {...styles.strengthItem, backgroundColor: colorAccent};
  39. } else {
  40. return styles.strengthItem;
  41. }
  42. }
  43. return (
  44. <>
  45. <Text style={getStyle(1)}></Text>
  46. <Text style={getStyle(2)}></Text>
  47. <Text style={getStyle(3)}></Text>
  48. {/* <Text style={getStyle(4)}></Text> */}
  49. {/* <Text style={getStyle(5)}></Text> */}
  50. </>
  51. );
  52. };
  53. export default class Register extends React.Component {
  54. constructor(props) {
  55. super(props);
  56. this.state = {
  57. agree: true,
  58. strength: 0,
  59. countryNum: '65',
  60. countryShow: false,
  61. userInfo: {},
  62. countryNums: [],
  63. wrongCount: 0,
  64. params: {...this.props.route.params},
  65. email: '',
  66. password: '',
  67. fleetCompanyId: '',
  68. visible: false,
  69. isFleetDriver: false,
  70. pdvImages: ['', ''],
  71. companyList: []
  72. };
  73. }
  74. componentDidMount() {
  75. //console.log(this.state.params);
  76. this.getCountryList();
  77. this.getCompanyList();
  78. }
  79. applyStrength(text) {
  80. var strength = 0;
  81. if (text.length >= 8) {
  82. strength += 1;
  83. }
  84. if (/\d{1,}/.test(text)) {
  85. strength += 1;
  86. }
  87. if (/[A-z]{1,}/.test(text)) {
  88. strength += 1;
  89. }
  90. /*if (/[A-Z]{1,}/.test(text)) {
  91. strength += 1;
  92. }
  93. /*if (/\W{1,}/.test(text)) {
  94. strength += 1;
  95. }*/
  96. if (this.state.strength != strength) {
  97. this.setState({
  98. password: text,
  99. strength: strength
  100. });
  101. } else {
  102. this.setState({
  103. password: text
  104. });
  105. }
  106. }
  107. changeInfo(key, value) {
  108. var info = this.state.userInfo;
  109. info[key] = value;
  110. this.setState({
  111. 'userInfo': info
  112. });
  113. }
  114. changeTab(type) {
  115. this.setState({
  116. isFleetDriver: type
  117. })
  118. }
  119. getCountryList() {
  120. GetCountryList(list => {
  121. this.setState({
  122. countryNums: list
  123. })
  124. })
  125. }
  126. getCompanyList() {
  127. apiUser.getConmpany().then(res => {
  128. if (res.data) {
  129. this.setState({
  130. companyList: res.data
  131. })
  132. }
  133. }).catch(err => [
  134. toastShort(err)
  135. ])
  136. }
  137. onRegister() {
  138. //console.log('sign up', this.state);
  139. var info = this.state.userInfo;
  140. if (!info.nickName) {
  141. toastShort('Please enter display name');
  142. return;
  143. }
  144. if (!info.email) {
  145. toastShort('Please enter email address');
  146. return;
  147. }
  148. if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
  149. toastShort('Email is incorrect format');
  150. return;
  151. }
  152. if (!info.phone) {
  153. toastShort('Please enter contact number');
  154. return;
  155. }
  156. if (!/^\d{6,15}$/.test(info.phone)) {
  157. toastShort('Phone Number is incorrect format');
  158. return;
  159. }
  160. if (!this.state.password) {
  161. toastShort('Please enter password');
  162. return;
  163. }
  164. if (this.state.strength < 3) {
  165. toastShort('Password is not strong');
  166. return;
  167. }
  168. if (!info.password) {
  169. toastShort('Please enter confirm password');
  170. return;
  171. }
  172. if (info.password != this.state.password) {
  173. toastShort('The twice passwords are inconsistent');
  174. if (this.state.wrongCount < 3) {
  175. this.setState({
  176. wrongCount: this.state.wrongCount + 1
  177. })
  178. }
  179. return;
  180. }
  181. if (this.state.isFleetDriver) {
  182. if (!info.pdvLicence) {
  183. toastShort('Please enter PDV Licence');
  184. return;
  185. }
  186. if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
  187. toastShort('Please upload PDV Licence Photos');
  188. return;
  189. }
  190. }
  191. let param = Object.assign({}, info);
  192. //param.phone = this.state.countryNum + info.phone
  193. param.callingCode = this.state.countryNum;
  194. if (this.state.isFleetDriver) {
  195. param.userType = 'Driver';
  196. param.pdvLicencePictures = this.state.pdvImages;
  197. param.fleetCompanyId = this.state.fleetCompanyId;
  198. } else {
  199. param.userType = 'Public';
  200. }
  201. console.log('params', param);
  202. Dialog.showProgressDialog();
  203. apiUser.register(param).then(res => {
  204. Dialog.dismissLoading();
  205. //toastShort('Sign up successfully!');
  206. this.setState({
  207. email: param.email,
  208. visible: true
  209. });
  210. //this.backToLogin();
  211. }).catch(err => {
  212. toastShort(err);
  213. Dialog.dismissLoading();
  214. });
  215. }
  216. getBackTopPosition() {
  217. return isIOS ? statusHeight - 16 : 8;
  218. }
  219. backToLogin() {
  220. if (this.state.params.actionLogin) {
  221. goBack()
  222. startPage(PageList.login);
  223. } else {
  224. goBack();
  225. }
  226. }
  227. uploadImage(index) {
  228. ImagePicker.openPicker(options).then(image => {
  229. if (image.path) {
  230. apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
  231. if (res.success && res.data.picturePath) {
  232. let imageUrl = this.state.pdvImages;
  233. imageUrl[index] = res.data.picturePath;
  234. this.setState({
  235. pdvImages: imageUrl
  236. });
  237. } else {
  238. toastShort('Upload failed, please retry');
  239. }
  240. }).catch(err => {
  241. toastShort(err);
  242. });
  243. }
  244. }).catch(err => {
  245. //console.log(err);
  246. });
  247. }
  248. changeAgree(ag) {
  249. this.setState({
  250. agree: ag
  251. })
  252. }
  253. hideDialog() {
  254. this.setState({
  255. visible: false
  256. });
  257. this.backToLogin();
  258. }
  259. render() {
  260. return (
  261. <View style={StyleSheet.absoluteFillObject}>
  262. <ScrollView
  263. style={styles.scollView}
  264. keyboardShouldPersistTaps={'handled'}>
  265. <View style={styles.header}>
  266. <View style={styles.logoView}>
  267. {/* <Image
  268. style={styles.logoImg}
  269. source={require('../../images/tool-logo.png')}/> */}
  270. </View>
  271. </View>
  272. <View style={{...styles.backView, top: this.getBackTopPosition()}}>
  273. <Button
  274. style={styles.backButton}
  275. viewStyle={styles.backButtonView}
  276. onClick={() => goBack()}>
  277. <BackIcon/>
  278. <Text style={{color: '#333',fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
  279. </Button>
  280. </View>
  281. <View style={styles.signView}>
  282. <View style={styles.tabView}>
  283. <Text
  284. style={[
  285. styles.tabText,
  286. this.state.isFleetDriver ? {} : styles.tabActive
  287. ]}
  288. onPress={() => this.changeTab(false)}
  289. >Public Users</Text>
  290. <Text
  291. style={[
  292. styles.tabText,
  293. this.state.isFleetDriver ? styles.tabActive: {}
  294. ]}
  295. onPress={() => this.changeTab(true)}
  296. >Fleet/PH Drivers</Text>
  297. </View>
  298. <Text style={styles.title}>Registration</Text>
  299. <View style={styles.signInput}>
  300. <Text style={styles.inputLabel}>Display Name</Text>
  301. <TextInput
  302. style={styles.inputView}
  303. placeholder='Display Name'
  304. maxLength={50}
  305. onChangeText={v => this.changeInfo('nickName', v)}
  306. />
  307. </View>
  308. <View style={styles.signInput}>
  309. <Text style={styles.inputLabel}>Email Address</Text>
  310. <TextInput
  311. style={styles.inputView}
  312. placeholder='Email Address'
  313. maxLength={50}
  314. onChangeText={v => this.changeInfo('email', v)}
  315. />
  316. </View>
  317. <View style={styles.signInput}>
  318. <Text style={styles.inputLabel}>Phone Number</Text>
  319. <View style={styles.mobileView}>
  320. <View style={styles.dropView}>
  321. <TextInput style={styles.dropInput} editable={false}/>
  322. <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
  323. <MaterialIcons name={'arrow-drop-down'} size={24} color={'#333'}/>
  324. <Dropdown
  325. style={styles.dropLayer}
  326. title='Country'
  327. prefixText="+"
  328. list={this.state.countryNums}
  329. value={this.state.countryNum}
  330. nameKey='countryNum'
  331. valueKey='countryNum'
  332. onChange={(value, index)=> {
  333. this.setState({
  334. countryNum: value
  335. })
  336. }}
  337. customerItemView={
  338. (item, index, onClick) =>
  339. <CountryDropNum
  340. key={index}
  341. country={item}
  342. value={this.state.countryNum}
  343. onClick={onClick}/>
  344. }/>
  345. </View>
  346. <TextInput
  347. style={styles.contactView}
  348. placeholder='Mobile Number'
  349. keyboardType='phone-pad'
  350. maxLength={15}
  351. onChangeText={v => this.changeInfo('phone', v)}
  352. />
  353. </View>
  354. </View>
  355. <View style={styles.signInput}>
  356. <Text style={styles.inputLabel}>Create Password</Text>
  357. <TextInput
  358. secureTextEntry={this.state.wrongCount < 3}
  359. style={styles.inputView}
  360. placeholder='Password'
  361. maxLength={20}
  362. onChangeText={(value) => {
  363. this.applyStrength(value);
  364. }}
  365. />
  366. </View>
  367. <View style={styles.signInput}>
  368. <Text style={styles.inputLabel}></Text>
  369. <View style={styles.passwordView}>
  370. <View style={styles.strengthView}>
  371. <Text style={{fontSize:12, paddingRight: 4, color: '#333'}}>Password Strength</Text>
  372. <StrengthView {...this.state}/>
  373. </View>
  374. <View>
  375. <Text style={styles.passwordRole}>Your Password Must Have:</Text>
  376. <Text style={styles.passwordRole}>- 8 or more characters</Text>
  377. {/* <Text style={styles.passwordRole}>- upper and lower case letters</Text> */}
  378. <Text style={styles.passwordRole}>- at least one number</Text>
  379. </View>
  380. </View>
  381. </View>
  382. <View style={styles.signInput}>
  383. <Text style={styles.inputLabel}>Confirm Password</Text>
  384. <TextInput
  385. secureTextEntry={this.state.wrongCount < 3}
  386. style={styles.inputView}
  387. placeholder='Password'
  388. maxLength={20}
  389. onChangeText={v => this.changeInfo('password', v)}
  390. />
  391. </View>
  392. { this.state.isFleetDriver &&
  393. <>
  394. <View style={styles.signInput}>
  395. <Text style={styles.inputLabel}>Your Company</Text>
  396. <Dropdown
  397. style={[styles.inputView, ui.flexc]}
  398. title='Company'
  399. list={this.state.companyList}
  400. value={this.state.fleetCompanyId}
  401. valueKey='fleetCompanyId'
  402. nameKey='fleetCompanyName'
  403. onChange={(value, index)=> {
  404. this.setState({
  405. fleetCompanyId: value
  406. })
  407. }}/>
  408. </View>
  409. <View style={styles.signInput}>
  410. <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
  411. <TextInput
  412. style={styles.inputView}
  413. placeholder='PH Driver Vocational Licence'
  414. maxLength={20}
  415. onChangeText={v => this.changeInfo('pdvLicence', v)}
  416. />
  417. </View>
  418. <View style={styles.signInput}>
  419. <Text style={styles.inputLabel}>{' PDV Photos\n(Front & Back)'}</Text>
  420. <View style={styles.uploadGroup}>
  421. { this.state.pdvImages.map((item, index) => (
  422. <UploadView
  423. key={index}
  424. onPress={() => this.uploadImage(index)}
  425. url={item}/>
  426. ))
  427. }
  428. </View>
  429. </View>
  430. </>
  431. }
  432. <View style={styles.referView}>
  433. <Text style={styles.referTitle}>Have a referral code?</Text>
  434. <View style={styles.referText}>
  435. <Text>You'll get</Text>
  436. <Text style={styles.weight}>$5</Text>
  437. <Text>Credit as registration bonus!</Text>
  438. </View>
  439. <View style={styles.codeView}>
  440. <Text style={{ color: '#333', fontSize: 16 }}>Referral Code</Text>
  441. <TextInput
  442. style={styles.codeText}
  443. maxLength={6}
  444. placeholder='Referral Code'
  445. onChangeText={v => this.changeInfo('referralCode', v)}
  446. />
  447. </View>
  448. </View>
  449. <View style={styles.agreeView}>
  450. <CheckBox
  451. value={this.state.agree}
  452. onValueChange={v => this.changeAgree(v)}
  453. />
  454. <View style={styles.agreeTextRow}>
  455. <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
  456. {'I have read and I agree with the '}
  457. </Text>
  458. <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
  459. <Text style={styles.agreeText}>{' '}</Text>
  460. <Text style={styles.agreeText}>{'and '}</Text>
  461. <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
  462. <Text style={styles.agreeText}>.</Text>
  463. </View>
  464. </View>
  465. <Button
  466. style={styles.signButton}
  467. elevation={1.5}
  468. disabled={!this.state.agree}
  469. text='SIGN UP'
  470. fontSize={14}
  471. onClick={() => {
  472. this.onRegister();
  473. }}
  474. />
  475. </View>
  476. </ScrollView>
  477. <Modal
  478. isVisible={this.state.visible}
  479. onBackButtonPress={() => this.hideDialog()}
  480. {...ModalProps}>
  481. <RegisterDialog
  482. address={this.state.email}
  483. onClose={() => this.hideDialog()}
  484. />
  485. </Modal>
  486. </View>
  487. );
  488. }
  489. }
  490. const UploadView = ({url, onPress}) => (
  491. <Pressable
  492. style={styles.uploadView}
  493. onPress={onPress}>
  494. { url == ''
  495. ? <Image
  496. style={styles.uploadIcon}
  497. source={require('../../images/icon/ic-add-photo.png')}/>
  498. : <Image
  499. style={styles.uploadIcon}
  500. defaultSource={require('../../images/icon/icon-upload-default.png')}
  501. source={{uri: host + url}}/>
  502. }
  503. </Pressable>
  504. )
  505. const styles = StyleSheet.create({
  506. header: {
  507. paddingTop: 56,
  508. backgroundColor: colorThemes
  509. },
  510. backView: {
  511. top: 12,
  512. zIndex: 5,
  513. padding: 16,
  514. position: 'absolute',
  515. flexDirection: 'row'
  516. },
  517. backButton: {
  518. borderRadius: 60,
  519. backgroundColor: 'white'
  520. },
  521. backButtonView: {
  522. height: 43,
  523. paddingLeft: 16,
  524. paddingRight: 16,
  525. alignItems: 'center',
  526. flexDirection: 'row'
  527. },
  528. scollView: {
  529. flex: 1
  530. },
  531. logoView: {
  532. paddingTop: 32,
  533. paddingBottom: 56,
  534. alignItems: 'center'
  535. },
  536. logoImg: {
  537. width:165.09,
  538. height: 51.94,
  539. },
  540. signView: {
  541. flex: 1,
  542. padding: 16,
  543. marginTop: -30,
  544. borderTopLeftRadius: 20,
  545. borderTopRightRadius: 20,
  546. backgroundColor: 'white'
  547. },
  548. tabView: {
  549. marginBottom: 4,
  550. borderWidth: 1,
  551. borderRadius: 6,
  552. overflow: 'hidden',
  553. alignItems: 'center',
  554. flexDirection: 'row',
  555. borderColor: colorPrimary,
  556. },
  557. tabText: {
  558. flex: 1,
  559. color: '#333',
  560. fontSize: 15,
  561. textAlign: 'center',
  562. ...$padding(10, 0),
  563. },
  564. tabActive: {
  565. color: '#fff',
  566. fontWeight: 'bold',
  567. backgroundColor: colorPrimary
  568. },
  569. title: {
  570. color: '#333',
  571. fontSize: 18,
  572. fontWeight: '700',
  573. paddingTop: 4,
  574. paddingBottom: 6,
  575. },
  576. signInput: {
  577. marginTop: 16,
  578. alignItems: 'center',
  579. flexDirection: 'row'
  580. },
  581. inputLabel: {
  582. flex: 1,
  583. color: '#333',
  584. fontSize: 14,
  585. marginRight: 4
  586. },
  587. inputView: {
  588. flex: 2,
  589. color: '#333',
  590. fontSize: 14,
  591. borderRadius: 5,
  592. minHeight: 40,
  593. paddingTop: 6,
  594. paddingLeft: 12,
  595. paddingRight: 12,
  596. paddingBottom: 6,
  597. backgroundColor: '#F5F5F5'
  598. },
  599. mobileView: {
  600. flex: 2,
  601. marginLeft: -12,
  602. alignItems: 'center',
  603. flexDirection: 'row'
  604. },
  605. dropView: {
  606. fontSize: 16,
  607. borderRadius: 4,
  608. paddingRight: 4,
  609. flexDirection: 'row',
  610. alignItems: 'center',
  611. backgroundColor: '#F5F5F5'
  612. },
  613. dropInput: {
  614. width: 12,
  615. padding: 6,
  616. color: '#333',
  617. minHeight: 40,
  618. },
  619. countryText: {
  620. color: '#333',
  621. fontSize: 14,
  622. paddingRight: 4
  623. },
  624. dropLayer: {
  625. left: 0,
  626. right: 0,
  627. opacity: 0,
  628. position: 'absolute'
  629. },
  630. contactView: {
  631. flex: 1,
  632. color: '#333',
  633. fontSize: 15,
  634. borderRadius: 4,
  635. minHeight: 40,
  636. paddingTop: 6,
  637. paddingLeft: 12,
  638. paddingRight: 12,
  639. paddingBottom: 6,
  640. marginLeft: 10,
  641. backgroundColor: '#F5F5F5'
  642. },
  643. passwordView: {
  644. flex: 2,
  645. marginTop: -8,
  646. },
  647. strengthView: {
  648. marginTop: -4,
  649. alignItems: 'center',
  650. paddingBottom: 4,
  651. flexDirection: 'row'
  652. },
  653. passwordRole: {
  654. color: '#999',
  655. fontSize: 10,
  656. lineHeight: 13
  657. },
  658. strengthItem: {
  659. width: 14,
  660. height: 2.5,
  661. marginLeft: 8,
  662. borderRadius: 3,
  663. backgroundColor: '#CCC'
  664. },
  665. referView: {
  666. padding: 16,
  667. marginTop: 24,
  668. borderRadius: 6,
  669. alignItems: 'center',
  670. backgroundColor: '#F5F5F5'
  671. },
  672. referTitle: {
  673. color: '#333',
  674. fontSize: 18,
  675. fontWeight: 'bold'
  676. },
  677. referText: {
  678. color: '#333',
  679. paddingTop: 4,
  680. alignItems: 'flex-end',
  681. flexDirection: 'row'
  682. },
  683. weight: {
  684. fontSize: 18,
  685. paddingLeft: 4,
  686. paddingRight: 4,
  687. color: colorAccent,
  688. fontWeight: 'bold'
  689. },
  690. codeView: {
  691. paddingTop: 16,
  692. alignItems: 'center',
  693. flexDirection: 'row'
  694. },
  695. codeText: {
  696. color: '#333',
  697. fontSize: 16,
  698. paddingTop: 6,
  699. paddingLeft: 12,
  700. paddingRight: 12,
  701. paddingBottom: 6,
  702. minHeight: 40,
  703. marginLeft: 16,
  704. borderRadius: 6,
  705. textAlign: 'center',
  706. backgroundColor: 'white'
  707. },
  708. signButton: {
  709. marginTop: 24,
  710. marginBottom: 8,
  711. },
  712. uploadGroup: {
  713. flex: 2,
  714. alignItems: 'center',
  715. flexDirection: 'row',
  716. justifyContent: 'center'
  717. },
  718. uploadView: {
  719. flex: 1,
  720. alignItems: 'center'
  721. },
  722. uploadIcon: {
  723. width: $vw(28),
  724. height: $vw(28),
  725. borderRadius: 6
  726. },
  727. agreeView: {
  728. marginTop: 24,
  729. marginBottom: 16,
  730. flexDirection: 'row',
  731. alignItems: 'flex-start',
  732. },
  733. agreeTextRow: {
  734. flex: 1,
  735. paddingTop: 4,
  736. paddingLeft: 8,
  737. flexWrap: 'wrap',
  738. flexDirection: 'row'
  739. },
  740. agreeText: {
  741. color: '#333',
  742. fontSize: 14,
  743. paddingTop: 2,
  744. paddingBottom: 2
  745. },
  746. agreeLink: {
  747. ...ui.link,
  748. fontSize: 14,
  749. paddingTop: 2,
  750. paddingBottom: 2,
  751. textDecorationLine: 'underline'
  752. }
  753. });