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/src/CONST.js b/src/CONST.js
index 71a3b4aba7bc..208636ff6fe4 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -898,6 +898,37 @@ 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`,
+ 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: 'https://www.instagram.com/expensify',
+ FACEBOOK: 'https://www.facebook.com/expensify',
+ LINKEDIN: 'https://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 e2ad0be35137..6d1b56ecaa27 100644
--- a/src/components/LocalePicker.js
+++ b/src/components/LocalePicker.js
@@ -9,6 +9,7 @@ import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
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 */
@@ -47,6 +48,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 26d165afc8bf..c98c2a5e9a72 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 6c87c1088e3c..9e2775d26757 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: 'ExpensifyHelp',
+ community: 'Comunidad',
+ privacy: 'Privacidad',
+ learnMore: 'Más Información',
+ aboutExpensify: 'Acerca de Expensify',
+ blog: 'Blog',
+ jobs: 'Empleo',
+ expensifyOrg: 'Expensify.org',
+ 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..91b8ac27be9d 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..0f6e099bc8c0
--- /dev/null
+++ b/src/pages/signin/SignInPageLayout/Footer.js
@@ -0,0 +1,208 @@
+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.NEWHELP_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 (
+
+
+
+
+ {_.map(columns, (column, i) => (
+
+
+ {props.translate(column.translationPath)}
+
+
+ {_.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..7ce7a0d94ef9 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
deleted file mode 100644
index ca2d15821960..000000000000
--- a/src/pages/signin/SignInPageLayout/signInPageStyles/index.ios.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import styles from '../../../../styles/styles';
-
-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 96056d68acd8..abc753e30ee4 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 */
@@ -249,7 +249,9 @@ class BaseValidateCodeForm extends React.Component {
/>
-
+
+
+
>
);
}
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 3f968fec7c54..b46a800105d8 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -668,13 +668,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 minimum height as style
+ * @param {Number} minHeight
+ * @returns {Object}
+ */
+function getMinimumHeight(minHeight) {
+ return {
+ minHeight,
};
}
@@ -831,6 +842,7 @@ export {
convertToLTR,
hasSafeAreas,
getHeight,
+ getMinimumHeight,
fade,
getHorizontalStackedAvatarBorderStyle,
getReportWelcomeBackgroundImageStyle,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 6a03fa32ff87..7637bf42119d 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -2918,6 +2918,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 722c0c8cb722..18a2bf617b2c 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 800c451f654a..375ee77a594a 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -100,5 +100,7 @@ export default {
modalTopBigIconHeight: 244,
modalWordmarkWidth: 154,
modalWordmarkHeight: 34,
+ verticalLogoHeight: 634,
+ verticalLogoWidth: 111,
badgeMaxWidth: 180,
};