diff --git a/assets/images/expensify-footer-logo-vertical.svg b/assets/images/expensify-footer-logo-vertical.svg
new file mode 100644
index 000000000000..58dc05ad2944
--- /dev/null
+++ b/assets/images/expensify-footer-logo-vertical.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/assets/images/expensify-footer-logo.svg b/assets/images/expensify-footer-logo.svg
new file mode 100644
index 000000000000..e664651b84fd
--- /dev/null
+++ b/assets/images/expensify-footer-logo.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/assets/images/social-facebook.svg b/assets/images/social-facebook.svg
new file mode 100644
index 000000000000..3a966653e688
--- /dev/null
+++ b/assets/images/social-facebook.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/assets/images/social-instagram.svg b/assets/images/social-instagram.svg
new file mode 100644
index 000000000000..79d4aadf374a
--- /dev/null
+++ b/assets/images/social-instagram.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/assets/images/social-linkedin.svg b/assets/images/social-linkedin.svg
new file mode 100644
index 000000000000..97a1da8962f4
--- /dev/null
+++ b/assets/images/social-linkedin.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/assets/images/social-podcast.svg b/assets/images/social-podcast.svg
new file mode 100644
index 000000000000..1366699b6823
--- /dev/null
+++ b/assets/images/social-podcast.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/assets/images/social-twitter.svg b/assets/images/social-twitter.svg
new file mode 100644
index 000000000000..6a90f95032bb
--- /dev/null
+++ b/assets/images/social-twitter.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/assets/images/social-youtube.svg b/assets/images/social-youtube.svg
new file mode 100644
index 000000000000..834314d27d27
--- /dev/null
+++ b/assets/images/social-youtube.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index ed9cdc3f6850..75ceab1d5286 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -177,29 +177,29 @@ PODS:
- GoogleUtilities/Network (~> 7.4)
- "GoogleUtilities/NSData+zlib (~> 7.4)"
- nanopb (~> 2.30908.0)
- - GoogleDataTransport (9.2.0):
+ - GoogleDataTransport (9.2.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/AppDelegateSwizzler (7.10.0):
+ - GoogleUtilities/AppDelegateSwizzler (7.11.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- - GoogleUtilities/Environment (7.10.0):
+ - GoogleUtilities/Environment (7.11.0):
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/ISASwizzler (7.10.0)
- - GoogleUtilities/Logger (7.10.0):
+ - GoogleUtilities/ISASwizzler (7.11.0)
+ - GoogleUtilities/Logger (7.11.0):
- GoogleUtilities/Environment
- - GoogleUtilities/MethodSwizzler (7.10.0):
+ - GoogleUtilities/MethodSwizzler (7.11.0):
- GoogleUtilities/Logger
- - GoogleUtilities/Network (7.10.0):
+ - GoogleUtilities/Network (7.11.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- - "GoogleUtilities/NSData+zlib (7.10.0)"
- - GoogleUtilities/Reachability (7.10.0):
+ - "GoogleUtilities/NSData+zlib (7.11.0)"
+ - GoogleUtilities/Reachability (7.11.0):
- GoogleUtilities/Logger
- - GoogleUtilities/UserDefaults (7.10.0):
+ - GoogleUtilities/UserDefaults (7.11.0):
- GoogleUtilities/Logger
- hermes-engine (0.70.4)
- libevent (2.1.12)
@@ -969,8 +969,8 @@ SPEC CHECKSUMS:
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91
- GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
- GoogleUtilities: bad72cb363809015b1f7f19beb1f1cd23c589f95
+ GoogleDataTransport: ea169759df570f4e37bdee1623ec32a7e64e67c4
+ GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f
hermes-engine: 3623325e0d0676a45fbc544d72c57dd79fce7446
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
diff --git a/src/CONST.js b/src/CONST.js
index accd263483f4..1749c52bd2ec 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -895,6 +895,38 @@ const CONST = {
LEAVE_ROOM: 'leaveRoom',
},
+ FOOTER: {
+ EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,
+ SPEND_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/spend-management`,
+ EXPENSE_REPORTS_URL: `${USE_EXPENSIFY_URL}/expense-reports`,
+ COMPANY_CARD_URL: `${USE_EXPENSIFY_URL}/company-credit-card`,
+ RECIEPT_SCANNING_URL: `${USE_EXPENSIFY_URL}/receipt-scanning-app`,
+ BILL_PAY_URL: `${USE_EXPENSIFY_URL}/bills`,
+ INVOICES_URL: `${USE_EXPENSIFY_URL}/invoices`,
+ CPA_CARD_URL: `${USE_EXPENSIFY_URL}/cpa-card`,
+ PAYROLL_URL: `${USE_EXPENSIFY_URL}/payroll`,
+ TRAVEL_URL: `${USE_EXPENSIFY_URL}/travel`,
+ EXPENSIFY_APPROVED_URL: `${USE_EXPENSIFY_URL}/accountants`,
+ PRESS_KIT_URL: 'https://we.are.expensify.com/press-kit',
+ SUPPORT_URL: `${USE_EXPENSIFY_URL}/support`,
+ HELP_URL: 'https://help.expensify.com/',
+ COMMUNITY_URL: 'https://community.expensify.com/',
+ PRIVACY_URL: `${USE_EXPENSIFY_URL}/privacy`,
+ ABOUT_URL: 'https://we.are.expensify.com/',
+ BLOG_URL: 'https://blog.expensify.com/',
+ JOBS_URL: 'https://we.are.expensify.com/apply',
+ ORG_URL: 'https://expensify.org/',
+ INVESTOR_RELATIONS_URL: 'https://ir.expensify.com/',
+ },
+
+ SOCIALS: {
+ PODCAST: 'https://we.are.expensify.com/podcast',
+ TWITTER: 'https://www.twitter.com/expensify',
+ INSTAGRAM: 'http://www.instagram.com/expensify',
+ FACEBOOK: 'https://www.facebook.com/expensify',
+ LINKEDIN: 'http://www.linkedin.com/company/expensify',
+ },
+
// These split the maximum decimal value of a signed 64-bit number (9,223,372,036,854,775,807) into parts where none of them are too big to fit into a 32-bit number, so that we can
// generate them each with a random number generator with only 32-bits of precision.
MAX_64BIT_LEFT_PART: 92233,
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index 5b562d790ae1..864ae755b15f 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -98,6 +98,14 @@ import Zoom from '../../../assets/images/zoom.svg';
import FallbackAvatar from '../../../assets/images/avatars/fallback-avatar.svg';
import FallbackWorkspaceAvatar from '../../../assets/images/avatars/fallback-workspace-avatar.svg';
import DragAndDrop from '../../../assets/images/drag-and-drop.svg';
+import ExpensifyFooterLogo from '../../../assets/images/expensify-footer-logo.svg';
+import ExpensifyFooterLogoVertical from '../../../assets/images/expensify-footer-logo-vertical.svg';
+import Twitter from '../../../assets/images/social-twitter.svg';
+import Youtube from '../../../assets/images/social-youtube.svg';
+import Facebook from '../../../assets/images/social-facebook.svg';
+import Podcast from '../../../assets/images/social-podcast.svg';
+import Linkedin from '../../../assets/images/social-linkedin.svg';
+import Instagram from '../../../assets/images/social-instagram.svg';
export {
ActiveRoomAvatar,
@@ -140,6 +148,8 @@ export {
Exit,
ExpensifyCard,
ExpensifyWordmark,
+ ExpensifyFooterLogo,
+ ExpensifyFooterLogoVertical,
Expand,
Eye,
EyeDisabled,
@@ -200,4 +210,10 @@ export {
Wallet,
Workspace,
Zoom,
+ Twitter,
+ Youtube,
+ Facebook,
+ Podcast,
+ Linkedin,
+ Instagram,
};
diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js
index 5be074168250..4d434f12e3ec 100644
--- a/src/components/LocalePicker.js
+++ b/src/components/LocalePicker.js
@@ -10,6 +10,7 @@ import CONST from '../CONST';
import * as Localize from '../libs/Localize';
import Picker from './Picker';
import styles from '../styles/styles';
+import themeColors from '../styles/themes/default';
const propTypes = {
/** Indicates which locale the user currently has selected */
@@ -51,6 +52,7 @@ const LocalePicker = props => (
size={props.size}
value={props.preferredLocale}
containerStyles={props.size === 'small' ? [styles.pickerContainerSmall] : []}
+ backgroundColor={themeColors.transparent}
/>
);
diff --git a/src/languages/en.js b/src/languages/en.js
index c2edea86b01e..2a8891a3bdf5 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -1182,4 +1182,33 @@ export default {
report: {
genericAddCommentFailureMessage: 'Unexpected error while posting the comment, please try again later',
},
+ footer: {
+ features: 'Features',
+ expenseManagement: 'Expense Management',
+ spendManagement: 'Spend Management',
+ expenseReports: 'Expense Reports',
+ companyCreditCard: 'Company Credit Card',
+ receiptScanningApp: 'Receipt Scanning App',
+ billPay: 'Bill Pay',
+ invoicing: 'Invoicing',
+ CPACard: 'CPA Card',
+ payroll: 'Payroll',
+ travel: 'Travel',
+ resources: 'Resources',
+ expensifyApproved: 'ExpensifyApproved!',
+ pressKit: 'Press Kit',
+ support: 'Support',
+ expensifyHelp: 'ExpensifyHelp',
+ community: 'Community',
+ privacy: 'Privacy',
+ learnMore: 'Learn More',
+ aboutExpensify: 'About Expensify',
+ blog: 'Blog',
+ jobs: 'Jobs',
+ expensifyOrg: 'Expensify.org',
+ investorRelations: 'Investor Relations',
+ getStarted: 'Get Started',
+ createAccount: 'Create a new account',
+ logIn: 'Log in',
+ },
};
diff --git a/src/languages/es.js b/src/languages/es.js
index 8c82b5e70135..ca3148248bd6 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -1184,4 +1184,33 @@ export default {
report: {
genericAddCommentFailureMessage: 'Error inesperado al agregar el comentario, por favor inténtalo más tarde',
},
+ footer: {
+ features: 'Características',
+ expenseManagement: 'Gestión de Gastos',
+ spendManagement: 'Control de Gastos',
+ expenseReports: 'Informes de Gastos',
+ companyCreditCard: 'Tarjeta de Crédito de Empresa',
+ receiptScanningApp: 'Aplicación de Escaneado de Recibos',
+ billPay: 'Pago de Facturas',
+ invoicing: 'Facturación',
+ CPACard: 'Tarjeta Para Contables',
+ payroll: 'Nómina',
+ travel: 'Viajes',
+ resources: 'Recursos',
+ expensifyApproved: 'ExpensifyApproved!',
+ pressKit: 'Kit de Prensa',
+ support: 'Soporte',
+ expensifyHelp: '',
+ community: 'Comunidad',
+ privacy: 'Privacidad',
+ learnMore: 'Más Información',
+ aboutExpensify: 'Acerca de Expensify',
+ blog: 'Blog',
+ jobs: 'Empleo',
+ expensifyOrg: '',
+ investorRelations: 'Relaciones Con Los Inversores',
+ getStarted: 'Comenzar',
+ createAccount: 'Crear Una Cuenta Nueva',
+ logIn: 'Conectarse',
+ },
};
diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions.js b/src/libs/Navigation/AppNavigator/defaultScreenOptions.js
index f22e7d905768..3a0c0604d901 100644
--- a/src/libs/Navigation/AppNavigator/defaultScreenOptions.js
+++ b/src/libs/Navigation/AppNavigator/defaultScreenOptions.js
@@ -1,6 +1,7 @@
const defaultScreenOptions = {
cardStyle: {
overflow: 'visible',
+ flex: 1,
},
headerShown: false,
animationTypeForReplace: 'pop',
diff --git a/src/pages/signin/Licenses.js b/src/pages/signin/Licenses.js
new file mode 100644
index 000000000000..c860efa93451
--- /dev/null
+++ b/src/pages/signin/Licenses.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import {View} from 'react-native';
+import styles from '../../styles/styles';
+import CONST from '../../CONST';
+import Text from '../../components/Text';
+import TextLink from '../../components/TextLink';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import LocalePicker from '../../components/LocalePicker';
+
+const currentYear = new Date().getFullYear();
+
+const Licenses = props => (
+ <>
+
+ {`© ${currentYear} Expensify`}
+
+
+ {props.translate('termsOfUse.phrase5')}
+
+ {' '}
+ {props.translate('termsOfUse.phrase6')}
+
+ .
+
+
+
+
+ >
+);
+
+Licenses.propTypes = {...withLocalizePropTypes};
+Licenses.displayName = 'Licenses';
+
+export default withLocalize(Licenses);
diff --git a/src/pages/signin/LoginForm.js b/src/pages/signin/LoginForm.js
index 07867403d050..97d3593eae34 100755
--- a/src/pages/signin/LoginForm.js
+++ b/src/pages/signin/LoginForm.js
@@ -17,7 +17,6 @@ import * as ValidationUtils from '../../libs/ValidationUtils';
import * as LoginUtils from '../../libs/LoginUtils';
import withToggleVisibilityView, {toggleVisibilityViewPropTypes} from '../../components/withToggleVisibilityView';
import FormAlertWithSubmitButton from '../../components/FormAlertWithSubmitButton';
-import OfflineIndicator from '../../components/OfflineIndicator';
import {withNetwork} from '../../components/OnyxProvider';
import networkPropTypes from '../../components/networkPropTypes';
import * as ErrorUtils from '../../libs/ErrorUtils';
@@ -211,7 +210,6 @@ class LoginForm extends React.Component {
)
}
-
>
);
}
diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js
index 7d8813313089..f2f3c88f0c8e 100755
--- a/src/pages/signin/PasswordForm.js
+++ b/src/pages/signin/PasswordForm.js
@@ -23,8 +23,8 @@ import canFocusInputOnScreenFocus from '../../libs/canFocusInputOnScreenFocus';
import * as ErrorUtils from '../../libs/ErrorUtils';
import {withNetwork} from '../../components/OnyxProvider';
import networkPropTypes from '../../components/networkPropTypes';
-import OfflineIndicator from '../../components/OfflineIndicator';
import FormHelpMessage from '../../components/FormHelpMessage';
+import Terms from './Terms';
const propTypes = {
/* Onyx Props */
@@ -225,7 +225,9 @@ class PasswordForm extends React.Component {
/>
-
+
+
+
>
);
}
diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js
index 7753faa65294..c6230e951be6 100755
--- a/src/pages/signin/ResendValidationForm.js
+++ b/src/pages/signin/ResendValidationForm.js
@@ -14,7 +14,6 @@ import compose from '../../libs/compose';
import redirectToSignIn from '../../libs/actions/SignInRedirect';
import Avatar from '../../components/Avatar';
import * as ReportUtils from '../../libs/ReportUtils';
-import OfflineIndicator from '../../components/OfflineIndicator';
import networkPropTypes from '../../components/networkPropTypes';
import {withNetwork} from '../../components/OnyxProvider';
import DotIndicatorMessage from '../../components/DotIndicatorMessage';
@@ -93,7 +92,6 @@ const ResendValidationForm = (props) => {
isDisabled={props.network.isOffline}
/>
-
>
);
};
diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js
index d74119c2907e..323c166b3ec4 100644
--- a/src/pages/signin/SignInPage.js
+++ b/src/pages/signin/SignInPage.js
@@ -1,10 +1,8 @@
import React, {Component} from 'react';
-import {
- SafeAreaView,
-} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
+import {SafeAreaView} from 'react-native-safe-area-context';
import ONYXKEYS from '../../ONYXKEYS';
import styles from '../../styles/styles';
import compose from '../../libs/compose';
diff --git a/src/pages/signin/SignInPageLayout/Footer.js b/src/pages/signin/SignInPageLayout/Footer.js
new file mode 100644
index 000000000000..c7970277276f
--- /dev/null
+++ b/src/pages/signin/SignInPageLayout/Footer.js
@@ -0,0 +1,210 @@
+import {View} from 'react-native';
+import React from 'react';
+import _ from 'underscore';
+import Text from '../../../components/Text';
+import styles from '../../../styles/styles';
+import variables from '../../../styles/variables';
+import * as Expensicons from '../../../components/Icon/Expensicons';
+import TextLink from '../../../components/TextLink';
+import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
+import compose from '../../../libs/compose';
+import Licenses from '../Licenses';
+import Socials from '../Socials';
+import Hoverable from '../../../components/Hoverable';
+import CONST from '../../../CONST';
+
+const propTypes = {
+ ...windowDimensionsPropTypes,
+ ...withLocalizePropTypes,
+};
+
+const columns = [
+ {
+ translationPath: 'footer.features',
+ rows: [
+ {
+ link: CONST.FOOTER.EXPENSE_MANAGEMENT_URL,
+ translationPath: 'footer.expenseManagement',
+ },
+ {
+ link: CONST.FOOTER.SPEND_MANAGEMENT_URL,
+ translationPath: 'footer.spendManagement',
+ },
+ {
+ link: CONST.FOOTER.EXPENSE_REPORTS_URL,
+ translationPath: 'footer.expenseReports',
+ },
+ {
+ link: CONST.FOOTER.COMPANY_CARD_URL,
+ translationPath: 'footer.companyCreditCard',
+ },
+ {
+ link: CONST.FOOTER.RECIEPT_SCANNING_URL,
+ translationPath: 'footer.receiptScanningApp',
+ },
+ {
+ link: CONST.FOOTER.BILL_PAY_URL,
+ translationPath: 'footer.billPay',
+ },
+ {
+ link: CONST.FOOTER.INVOICES_URL,
+ translationPath: 'footer.invoicing',
+ },
+ {
+ link: CONST.FOOTER.CPA_CARD_URL,
+ translationPath: 'footer.CPACard',
+ },
+ {
+ link: CONST.FOOTER.PAYROLL_URL,
+ translationPath: 'footer.payroll',
+ },
+ {
+ link: CONST.FOOTER.TRAVEL_URL,
+ translationPath: 'footer.travel',
+ },
+ ],
+ },
+ {
+ translationPath: 'footer.resources',
+ rows: [
+ {
+ link: CONST.FOOTER.EXPENSIFY_APPROVED_URL,
+ translationPath: 'footer.expensifyApproved',
+ },
+ {
+ link: CONST.FOOTER.PRESS_KIT_URL,
+ translationPath: 'footer.pressKit',
+ },
+ {
+ link: CONST.FOOTER.SUPPORT_URL,
+ translationPath: 'footer.support',
+ },
+ {
+ link: CONST.FOOTER.HELP_URL,
+ translationPath: 'footer.expensifyHelp',
+ },
+ {
+ link: CONST.FOOTER.COMMUNITY_URL,
+ translationPath: 'footer.community',
+ },
+ {
+ link: CONST.FOOTER.PRIVACY_URL,
+ translationPath: 'footer.privacy',
+ },
+ ],
+ },
+ {
+ translationPath: 'footer.learnMore',
+ rows: [
+ {
+ link: CONST.FOOTER.ABOUT_URL,
+ translationPath: 'footer.aboutExpensify',
+ },
+ {
+ link: CONST.FOOTER.BLOG_URL,
+ translationPath: 'footer.blog',
+ },
+ {
+ link: CONST.FOOTER.JOBS_URL,
+ translationPath: 'footer.jobs',
+ },
+ {
+ link: CONST.FOOTER.ORG_URL,
+ translationPath: 'footer.expensifyOrg',
+ },
+ {
+ link: CONST.FOOTER.INVESTOR_RELATIONS_URL,
+ translationPath: 'footer.investorRelations',
+ },
+ ],
+ },
+ {
+ translationPath: 'footer.getStarted',
+ rows: [
+ {
+ link: CONST.NEW_EXPENSIFY_URL,
+ translationPath: 'footer.createAccount',
+ },
+ {
+ link: CONST.NEW_EXPENSIFY_URL,
+ translationPath: 'footer.logIn',
+ },
+ ],
+ },
+];
+
+const Footer = (props) => {
+ const isVertical = props.isSmallScreenWidth;
+ const imageDirection = isVertical ? styles.flexRow : styles.flexColumn;
+ const imageStyle = isVertical ? styles.pr0 : styles.alignSelfCenter;
+ const columnDirection = isVertical ? styles.flexColumn : styles.flexRow;
+ const pageFooterWrapper = [styles.footerWrapper, imageDirection, imageStyle];
+ const footerColumns = [styles.footerColumnsContainer, columnDirection];
+ const footerColumn = isVertical ? [styles.p4] : [styles.p4, props.isMediumScreenWidth ? styles.w50 : styles.w25];
+
+ return (
+
+
+
+
+ { /** Columns * */ }
+ {_.map(columns, (column, i) => (
+
+
+ {props.translate(column.translationPath)}
+
+
+ { /** Rows * */ }
+ {_.map(column.rows, row => (
+
+ {hovered => (
+
+ {props.translate(row.translationPath)}
+
+ )}
+
+ ))}
+ {(i === 2) && (
+
+
+
+ )}
+ {(i === 3) && (
+
+
+
+ )}
+
+
+ ))}
+
+
+ {!isVertical
+ ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+Footer.propTypes = propTypes;
+Footer.displayName = 'Footer';
+
+export default compose(
+ withLocalize,
+ withWindowDimensions,
+)(Footer);
diff --git a/src/pages/signin/SignInPageLayout/SignInPageContent.js b/src/pages/signin/SignInPageLayout/SignInPageContent.js
index 60d99b6ea2a2..76f03ec87715 100755
--- a/src/pages/signin/SignInPageLayout/SignInPageContent.js
+++ b/src/pages/signin/SignInPageLayout/SignInPageContent.js
@@ -6,12 +6,12 @@ import styles from '../../../styles/styles';
import variables from '../../../styles/variables';
import ExpensifyCashLogo from '../../../components/ExpensifyCashLogo';
import Text from '../../../components/Text';
-import TermsAndLicenses from '../TermsAndLicenses';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import SignInPageForm from '../../../components/SignInPageForm';
import compose from '../../../libs/compose';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView';
+import OfflineIndicator from '../../../components/OfflineIndicator';
const propTypes = {
/** The children to show inside the layout */
@@ -32,7 +32,7 @@ const SignInPageContent = props => (
(
{props.children}
-
-
-
+
+
+
);
diff --git a/src/pages/signin/SignInPageLayout/SignInPageGraphics.js b/src/pages/signin/SignInPageLayout/SignInPageGraphics.js
index c735bc9fd283..217a4713836c 100644
--- a/src/pages/signin/SignInPageLayout/SignInPageGraphics.js
+++ b/src/pages/signin/SignInPageLayout/SignInPageGraphics.js
@@ -1,17 +1,21 @@
import {Pressable} from 'react-native';
import React from 'react';
import _ from 'underscore';
-import styles from '../../../styles/styles';
import * as StyleUtils from '../../../styles/StyleUtils';
import * as Link from '../../../libs/actions/Link';
import SVGImage from '../../../components/SVGImage';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
const backgroundStyle = StyleUtils.getLoginPagePromoStyle();
-const SignInPageGraphics = () => (
+const propTypes = {
+ ...windowDimensionsPropTypes,
+};
+
+const SignInPageGraphics = props => (
{
@@ -29,5 +33,6 @@ const SignInPageGraphics = () => (
);
SignInPageGraphics.displayName = 'SignInPageGraphics';
+SignInPageGraphics.propTypes = propTypes;
-export default SignInPageGraphics;
+export default withWindowDimensions(SignInPageGraphics);
diff --git a/src/pages/signin/SignInPageLayout/index.js b/src/pages/signin/SignInPageLayout/index.js
index c165b336bb3d..bd0b4b52b0c5 100644
--- a/src/pages/signin/SignInPageLayout/index.js
+++ b/src/pages/signin/SignInPageLayout/index.js
@@ -1,10 +1,15 @@
import React from 'react';
-import {View} from 'react-native';
+import {View, ScrollView} from 'react-native';
+import {withSafeAreaInsets} from 'react-native-safe-area-context';
import PropTypes from 'prop-types';
+import compose from '../../../libs/compose';
import SignInPageContent from './SignInPageContent';
+import Footer from './Footer';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import styles from '../../../styles/styles';
import SignInPageGraphics from './SignInPageGraphics';
+import * as StyleUtils from '../../../styles/StyleUtils';
+import scrollViewContentContainerStyles from './signInPageStyles';
const propTypes = {
/** The children to show inside the layout */
@@ -24,24 +29,51 @@ const SignInPageLayout = (props) => {
let containerStyles = [styles.flex1, styles.signInPageInner];
let contentContainerStyles = [styles.flex1, styles.flexRow];
+ // To scroll on both mobile and web, we need to set the container height manually
+ const containerHeight = props.windowHeight - props.insets.top - props.insets.bottom;
+
if (props.isSmallScreenWidth) {
containerStyles = [styles.flex1];
- contentContainerStyles = [styles.flex1];
+ contentContainerStyles = [styles.flex1, styles.flexColumn];
}
return (
-
-
- {props.children}
-
- {!props.isSmallScreenWidth && (
-
+ {!props.isSmallScreenWidth
+ ? (
+
+
+ {props.children}
+
+
+
+
+
+
+ ) : (
+
+
+
+ {props.children}
+
+
+
+
+
+
)}
-
);
};
@@ -49,4 +81,7 @@ const SignInPageLayout = (props) => {
SignInPageLayout.propTypes = propTypes;
SignInPageLayout.displayName = 'SignInPageLayout';
-export default withWindowDimensions(SignInPageLayout);
+export default compose(
+ withWindowDimensions,
+ withSafeAreaInsets,
+)(SignInPageLayout);
diff --git a/src/pages/signin/SignInPageLayout/signInPageStyles/index.ios.js b/src/pages/signin/SignInPageLayout/signInPageStyles/index.ios.js
index ca2d15821960..004534709783 100644
--- a/src/pages/signin/SignInPageLayout/signInPageStyles/index.ios.js
+++ b/src/pages/signin/SignInPageLayout/signInPageStyles/index.ios.js
@@ -1,5 +1,7 @@
import styles from '../../../../styles/styles';
+// Using flexGrow on mobile allows the ScrollView container to grow to fit the content.
+// This is necessary because making the sign-in content fit exactly the viewheight causes the scroll to stop working on mobile.
const scrollViewContentContainerStyles = styles.flexGrow1;
export default scrollViewContentContainerStyles;
diff --git a/src/pages/signin/SignInPageLayout/signInPageStyles/index.js b/src/pages/signin/SignInPageLayout/signInPageStyles/index.js
index 9cb9a43bc197..de3cdcc20c0c 100644
--- a/src/pages/signin/SignInPageLayout/signInPageStyles/index.js
+++ b/src/pages/signin/SignInPageLayout/signInPageStyles/index.js
@@ -1,5 +1,6 @@
import styles from '../../../../styles/styles';
-const scrollViewContentContainerStyles = styles.mnh100;
+// On web, we can use flex to fit the content to fit the viewport within a ScrollView.
+const scrollViewContentContainerStyles = styles.flex1;
export default scrollViewContentContainerStyles;
diff --git a/src/pages/signin/SignInPageLayout/signInPageStyles/index.native.js b/src/pages/signin/SignInPageLayout/signInPageStyles/index.native.js
new file mode 100644
index 000000000000..004534709783
--- /dev/null
+++ b/src/pages/signin/SignInPageLayout/signInPageStyles/index.native.js
@@ -0,0 +1,7 @@
+import styles from '../../../../styles/styles';
+
+// Using flexGrow on mobile allows the ScrollView container to grow to fit the content.
+// This is necessary because making the sign-in content fit exactly the viewheight causes the scroll to stop working on mobile.
+const scrollViewContentContainerStyles = styles.flexGrow1;
+
+export default scrollViewContentContainerStyles;
diff --git a/src/pages/signin/Socials.js b/src/pages/signin/Socials.js
new file mode 100644
index 000000000000..a8f81e9e251e
--- /dev/null
+++ b/src/pages/signin/Socials.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import _ from 'underscore';
+import {Pressable, Linking} from 'react-native';
+import Icon from '../../components/Icon';
+import Text from '../../components/Text';
+import * as Expensicons from '../../components/Icon/Expensicons';
+import themeColors from '../../styles/themes/default';
+import styles from '../../styles/styles';
+import variables from '../../styles/variables';
+import CONST from '../../CONST';
+
+const socialsList = [
+ {
+ iconURL: Expensicons.Podcast,
+ link: CONST.SOCIALS.PODCAST,
+ },
+ {
+ iconURL: Expensicons.Twitter,
+ link: CONST.SOCIALS.TWITTER,
+ },
+ {
+ iconURL: Expensicons.Instagram,
+ link: CONST.SOCIALS.INSTAGRAM,
+ },
+ {
+ iconURL: Expensicons.Facebook,
+ link: CONST.SOCIALS.FACEBOOK,
+ },
+ {
+ iconURL: Expensicons.Linkedin,
+ link: CONST.SOCIALS.LINKEDIN,
+ },
+];
+
+const Socials = () => (
+
+ {_.map(socialsList, social => (
+ {
+ Linking.openURL(social.link);
+ }}
+ style={styles.pr1}
+ key={social.link}
+ >
+ {({hovered}) => (
+
+ )}
+
+ ))}
+
+);
+
+Socials.displayName = 'Socials';
+
+export default Socials;
diff --git a/src/pages/signin/Terms.js b/src/pages/signin/Terms.js
new file mode 100644
index 000000000000..81415bc75409
--- /dev/null
+++ b/src/pages/signin/Terms.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import styles from '../../styles/styles';
+import CONST from '../../CONST';
+import Text from '../../components/Text';
+import TextLink from '../../components/TextLink';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+
+const linkStyles = [styles.textExtraSmallSupporting, styles.link];
+
+const Terms = props => (
+
+ {props.translate('termsOfUse.phrase1')}
+
+ {' '}
+ {props.translate('termsOfUse.phrase2')}
+ {' '}
+
+ {props.translate('termsOfUse.phrase3')}
+
+ {' '}
+ {props.translate('termsOfUse.phrase4')}
+
+ {'. '}
+
+);
+
+Terms.propTypes = {...withLocalizePropTypes};
+Terms.displayName = 'Terms';
+
+export default withLocalize(Terms);
diff --git a/src/pages/signin/TermsAndLicenses.js b/src/pages/signin/TermsAndLicenses.js
deleted file mode 100644
index 498a8299779d..000000000000
--- a/src/pages/signin/TermsAndLicenses.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import {View} from 'react-native';
-import styles from '../../styles/styles';
-import defaultTheme from '../../styles/themes/default';
-import CONST from '../../CONST';
-import Text from '../../components/Text';
-import TextLink from '../../components/TextLink';
-import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
-import LogoWordmark from '../../../assets/images/expensify-wordmark.svg';
-import LocalePicker from '../../components/LocalePicker';
-
-const TermsAndLicenses = props => (
- <>
-
- {props.translate('termsOfUse.phrase1')}
-
- {' '}
- {props.translate('termsOfUse.phrase2')}
- {' '}
-
- {props.translate('termsOfUse.phrase3')}
-
- {' '}
- {props.translate('termsOfUse.phrase4')}
-
- {'. '}
- {props.translate('termsOfUse.phrase5')}
-
- {' '}
- {props.translate('termsOfUse.phrase6')}
-
- .
-
-
-
-
-
- >
-);
-
-TermsAndLicenses.propTypes = {...withLocalizePropTypes};
-TermsAndLicenses.displayName = 'TermsAndLicenses';
-
-export default withLocalize(TermsAndLicenses);
diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js
index 9ae596c2fc7c..7227493b6c10 100755
--- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js
+++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js
@@ -22,9 +22,9 @@ import canFocusInputOnScreenFocus from '../../../libs/canFocusInputOnScreenFocus
import * as ErrorUtils from '../../../libs/ErrorUtils';
import {withNetwork} from '../../../components/OnyxProvider';
import networkPropTypes from '../../../components/networkPropTypes';
-import OfflineIndicator from '../../../components/OfflineIndicator';
import * as User from '../../../libs/actions/User';
import FormHelpMessage from '../../../components/FormHelpMessage';
+import Terms from '../Terms';
const propTypes = {
/* Onyx Props */
@@ -239,7 +239,9 @@ class BaseValidateCodeForm extends React.Component {
/>
-
+
+
+
>
);
}
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 04a5339fdecc..bdcdbdbde8bb 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -605,13 +605,24 @@ function hasSafeAreas(windowWidth, windowHeight) {
}
/**
- * Get variable keyboard height as style
- * @param {Number} keyboardHeight
+ * Get height as style
+ * @param {Number} height
* @returns {Object}
*/
-function getHeight(keyboardHeight) {
+function getHeight(height) {
return {
- height: keyboardHeight,
+ height,
+ };
+}
+
+/**
+ * Get height as style
+ * @param {Number} minHeight
+ * @returns {Object}
+ */
+function getMinHeight(minHeight) {
+ return {
+ minHeight,
};
}
@@ -768,6 +779,7 @@ export {
convertToLTR,
hasSafeAreas,
getHeight,
+ getMinHeight,
fade,
getHorizontalStackedAvatarBorderStyle,
getReportWelcomeBackgroundImageStyle,
diff --git a/src/styles/colors.js b/src/styles/colors.js
index 7af4003a67f6..3aee02c73e79 100644
--- a/src/styles/colors.js
+++ b/src/styles/colors.js
@@ -35,6 +35,9 @@ export default {
greenDefaultButtonDisabled: '#8BA69E',
midnight: '#002140',
+ // Brand Colors from Figma
+ green700: '#23503B',
+
// DEPRECATED COLORS. Do not reference these colors. Will be deleted in color switch PR.
gray1: '#FAFAFA',
gray2: '#ECECEC',
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 0f8457940c87..c74b5cd18b11 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -2917,6 +2917,43 @@ const styles = {
fontSize: variables.fontSizeXXLarge,
letterSpacing: 4,
},
+
+ footer: {
+ backgroundColor: themeColors.midtone,
+ },
+
+ footerWrapper: {
+ fontSize: variables.fontSizeNormal,
+ paddingTop: 64,
+ paddingHorizontal: 32,
+ maxWidth: 1100, // Match footer across all Expensify platforms
+ },
+
+ footerColumnsContainer: {
+ flex: 1,
+ flexWrap: 'wrap',
+ marginBottom: 40,
+ marginHorizontal: -16,
+ },
+
+ footerTitle: {
+ fontSize: variables.fontSizeLarge,
+ color: themeColors.success,
+ marginBottom: 16,
+ },
+
+ footerRow: {
+ paddingVertical: 4,
+ marginBottom: 8,
+ color: themeColors.textLight,
+ fontSize: variables.fontSizeMedium,
+ },
+
+ footerBottomLogo: {
+ marginTop: 40,
+ width: '100%',
+ },
+
};
export default styles;
diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js
index 618859ea6813..738cd917ab49 100644
--- a/src/styles/themes/default.js
+++ b/src/styles/themes/default.js
@@ -28,6 +28,7 @@ const darkTheme = {
successHover: colors.greenHover,
successPressed: colors.greenPressed,
transparent: colors.transparent,
+ midtone: colors.green700,
// Additional keys
overlay: colors.greenHighlightBackground,
diff --git a/src/styles/utilities/sizing.js b/src/styles/utilities/sizing.js
index 70826444f72d..a55774756075 100644
--- a/src/styles/utilities/sizing.js
+++ b/src/styles/utilities/sizing.js
@@ -12,6 +12,10 @@ export default {
width: '20%',
},
+ w25: {
+ width: '25%',
+ },
+
mnh100: {
minHeight: '100%',
},
diff --git a/src/styles/variables.js b/src/styles/variables.js
index 0e91a707d00a..78d385ec3c7c 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -98,4 +98,6 @@ export default {
modalTopBigIconHeight: 244,
modalWordmarkWidth: 154,
modalWordmarkHeight: 34,
+ verticalLogoHeight: 634,
+ verticalLogoWidth: 111,
};