Skip to content

Commit

Permalink
Merge pull request #42990 from MrMuzyk/feat/subsription-settings-ui
Browse files Browse the repository at this point in the history
feat: Subscription settings UI
  • Loading branch information
amyevans authored Jun 7, 2024
2 parents a4e20bf + 551789b commit eff0783
Show file tree
Hide file tree
Showing 16 changed files with 353 additions and 9 deletions.
22 changes: 22 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4815,6 +4815,28 @@ const CONST = {
},

SUBSCRIPTION_SIZE_LIMIT: 20000,
SUBSCRIPTION_POSSIBLE_COST_SAVINGS: {
COLLECT_PLAN: 10,
CONTROL_PLAN: 18,
},
FEEDBACK_SURVEY_OPTIONS: {
TOO_LIMITED: {
ID: 'tooLimited',
TRANSLATION_KEY: 'feedbackSurvey.tooLimited',
},
TOO_EXPENSIVE: {
ID: 'tooExpensive',
TRANSLATION_KEY: 'feedbackSurvey.tooExpensive',
},
INADEQUATE_SUPPORT: {
ID: 'inadequateSupport',
TRANSLATION_KEY: 'feedbackSurvey.inadequateSupport',
},
BUSINESS_CLOSING: {
ID: 'businessClosing',
TRANSLATION_KEY: 'feedbackSurvey.businessClosing',
},
},
} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const ROUTES = {
SETTINGS_SUBSCRIPTION: 'settings/subscription',
SETTINGS_SUBSCRIPTION_SIZE: 'settings/subscription/subscription-size',
SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD: 'settings/subscription/add-payment-card',
SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY: 'settings/subscription/disable-auto-renew-survey',
SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode',
SETTINGS_LANGUAGE: 'settings/preferences/language',
SETTINGS_THEME: 'settings/preferences/theme',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const SCREENS = {
ROOT: 'Settings_Subscription',
SIZE: 'Settings_Subscription_Size',
ADD_PAYMENT_CARD: 'Settings_Subscription_Add_Payment_Card',
DISABLE_AUTO_RENEW_SURVEY: 'Settings_Subscription_DisableAutoRenewSurvey',
},
},
SAVE_THE_WORLD: {
Expand Down
90 changes: 90 additions & 0 deletions src/components/FeedbackSurvey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, {useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import FixedFooter from './FixedFooter';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
import SingleOptionSelector from './SingleOptionSelector';
import Text from './Text';

type FeedbackSurveyProps = {
/** Title of the survey */
title: string;

/** Description of the survey */
description: string;

/** Callback to be called when the survey is submitted */
onSubmit: (reason: Option) => void;

/** Styles for the option row element */
optionRowStyles?: StyleProp<ViewStyle>;
};

type Option = {
key: string;
label: TranslationPaths;
};

const OPTIONS: Option[] = [
{key: CONST.FEEDBACK_SURVEY_OPTIONS.TOO_LIMITED.ID, label: CONST.FEEDBACK_SURVEY_OPTIONS.TOO_LIMITED.TRANSLATION_KEY},
{key: CONST.FEEDBACK_SURVEY_OPTIONS.TOO_EXPENSIVE.ID, label: CONST.FEEDBACK_SURVEY_OPTIONS.TOO_EXPENSIVE.TRANSLATION_KEY},
{key: CONST.FEEDBACK_SURVEY_OPTIONS.INADEQUATE_SUPPORT.ID, label: CONST.FEEDBACK_SURVEY_OPTIONS.INADEQUATE_SUPPORT.TRANSLATION_KEY},
{key: CONST.FEEDBACK_SURVEY_OPTIONS.BUSINESS_CLOSING.ID, label: CONST.FEEDBACK_SURVEY_OPTIONS.BUSINESS_CLOSING.TRANSLATION_KEY},
];

function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: FeedbackSurveyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();

const selectCircleStyles: StyleProp<ViewStyle> = {borderColor: theme.border};
const [reason, setReason] = useState<Option>();
const [shouldShowReasonError, setShouldShowReasonError] = useState(false);

const handleOptionSelect = (option: Option) => {
setShouldShowReasonError(false);
setReason(option);
};

const handleSubmit = () => {
if (!reason) {
setShouldShowReasonError(true);
return;
}

onSubmit(reason);
};

return (
<View style={[styles.flexGrow1, styles.justifyContentBetween]}>
<View style={styles.mh5}>
<Text style={styles.textHeadline}>{title}</Text>
<Text style={[styles.mt1, styles.mb3, styles.textNormalThemeText]}>{description}</Text>
<SingleOptionSelector
options={OPTIONS}
optionRowStyles={[styles.mb7, optionRowStyles]}
selectCircleStyles={selectCircleStyles}
selectedOptionKey={reason?.key}
onSelectOption={handleOptionSelect}
/>
</View>
<FixedFooter>
<FormAlertWithSubmitButton
isAlertVisible={shouldShowReasonError}
onSubmit={handleSubmit}
message="common.error.pleaseCompleteForm"
buttonText={translate('common.submit')}
/>
</FixedFooter>
</View>
);
}

FeedbackSurvey.displayName = 'FeedbackSurvey';

export default FeedbackSurvey;
13 changes: 10 additions & 3 deletions src/components/SingleOptionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -22,9 +23,15 @@ type SingleOptionSelectorProps = {

/** Function to be called when an option is selected */
onSelectOption?: (item: Item) => void;

/** Styles for the option row element */
optionRowStyles?: StyleProp<ViewStyle>;

/** Styles for the select circle */
selectCircleStyles?: StyleProp<ViewStyle>;
};

function SingleOptionSelector({options = [], selectedOptionKey, onSelectOption = () => {}}: SingleOptionSelectorProps) {
function SingleOptionSelector({options = [], selectedOptionKey, onSelectOption = () => {}, optionRowStyles, selectCircleStyles}: SingleOptionSelectorProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
return (
Expand All @@ -35,7 +42,7 @@ function SingleOptionSelector({options = [], selectedOptionKey, onSelectOption =
key={option.key}
>
<PressableWithoutFeedback
style={styles.singleOptionSelectorRow}
style={[styles.singleOptionSelectorRow, optionRowStyles]}
onPress={() => onSelectOption(option)}
role={CONST.ROLE.BUTTON}
accessibilityState={{checked: selectedOptionKey === option.key}}
Expand All @@ -44,7 +51,7 @@ function SingleOptionSelector({options = [], selectedOptionKey, onSelectOption =
>
<SelectCircle
isChecked={selectedOptionKey ? selectedOptionKey === option.key : false}
selectCircleStyles={[styles.ml0, styles.singleOptionSelectorCircle]}
selectCircleStyles={[styles.ml0, styles.singleOptionSelectorCircle, selectCircleStyles]}
/>
<Text>{translate(option.label)}</Text>
</PressableWithoutFeedback>
Expand Down
19 changes: 19 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export default {
enterAmount: 'Enter an amount.',
enterDate: 'Enter a date.',
invalidTimeRange: 'Please enter a time using the 12-hour clock format (e.g., 2:30 PM).',
pleaseCompleteForm: 'Please complete the form above to continue.',
},
comma: 'comma',
semicolon: 'semicolon',
Expand Down Expand Up @@ -3283,5 +3284,23 @@ export default {
security: 'Expensify is PCI-DSS compliant, uses bank-level encryption, and utilizes redundant infrastructure to protect your data.',
learnMoreAboutSecurity: 'Learn more about our security.',
},
subscriptionSettings: {
title: 'Subscription settings',
autoRenew: 'Auto-renew',
autoIncrease: 'Auto-increase annual seats',
saveUpTo: ({amountSaved}) => `Save up to $${amountSaved}/month per active member`,
automaticallyIncrease:
'Automatically increase your annual seats to accommodate for active members that exceed your subscription size. Note: This will extend your annual subscription end date.',
disableAutoRenew: 'Disable auto-renew',
helpUsImprove: 'Help us improve Expensify',
whatsMainReason: 'What’s the main reason you’re disabling auto-renew on your subscription?',
renewsOn: ({date}) => `Renews on ${date}`,
},
},
feedbackSurvey: {
tooLimited: 'Functionality needs improvement',
tooExpensive: 'Too expensive',
inadequateSupport: 'Inadequate customer support',
businessClosing: 'Company closing, downsizing, or acquired',
},
} satisfies TranslationBase;
19 changes: 19 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export default {
enterAmount: 'Introduce un importe.',
enterDate: 'Introduce una fecha.',
invalidTimeRange: 'Por favor, introduce una hora entre 1 y 12 (por ejemplo, 2:30 PM).',
pleaseCompleteForm: 'Por favor complete el formulario de arriba para continuar..',
},
comma: 'la coma',
semicolon: 'el punto y coma',
Expand Down Expand Up @@ -3790,5 +3791,23 @@ export default {
security: 'Expensify es PCI-DSS obediente, utiliza cifrado a nivel bancario, y emplea infraestructura redundante para proteger tus datos.',
learnMoreAboutSecurity: 'Conozca más sobre nuestra seguridad.',
},
subscriptionSettings: {
title: 'Configuración de suscripción',
autoRenew: 'Auto-renovación',
autoIncrease: 'Auto-incremento',
saveUpTo: ({amountSaved}) => `Ahorre hasta $${amountSaved} al mes por miembro activo`,
automaticallyIncrease:
'Aumenta automáticamente tus plazas anuales para dar lugar a los miembros activos que superen el tamaño de tu suscripción. Nota: Esto ampliará la fecha de finalización de tu suscripción anual.',
disableAutoRenew: 'Desactivar auto-renovación',
helpUsImprove: 'Ayúdanos a mejorar Expensify',
whatsMainReason: '¿Cuál es la razón principal por la que deseas desactivar la auto-renovación de tu suscripción?',
renewsOn: ({date}) => `Se renovará el ${date}`,
},
},
feedbackSurvey: {
tooLimited: 'Hay que mejorar la funcionalidad',
tooExpensive: 'Demasiado caro',
inadequateSupport: 'Atención al cliente inadecuada',
businessClosing: 'Cierre, reducción, o adquisición de la empresa',
},
} satisfies EnglishTranslation;
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType,
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../../pages/settings/Profile/CustomStatus/SetTimePage').default as React.ComponentType,
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require('../../../../pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage').default as React.ComponentType,
[SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: () => require('../../../../pages/settings/Subscription/DisableAutoRenewSurveyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage').default as React.ComponentType,
[SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType,
[SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> =
[SCREENS.SETTINGS.SAVE_THE_WORLD]: [SCREENS.I_KNOW_A_TEACHER, SCREENS.INTRO_SCHOOL_PRINCIPAL, SCREENS.I_AM_A_TEACHER],
[SCREENS.SETTINGS.TROUBLESHOOT]: [SCREENS.SETTINGS.CONSOLE],
[SCREENS.SEARCH.CENTRAL_PANE]: [SCREENS.SEARCH.REPORT_RHP],
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD, SCREENS.SETTINGS.SUBSCRIPTION.SIZE],
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD, SCREENS.SETTINGS.SUBSCRIPTION.SIZE, SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY],
};

export default CENTRAL_PANE_TO_RHP_MAPPING;
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_SIZE,
},
[SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY,
},
[SCREENS.WORKSPACE.CURRENCY]: {
path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import ScreenWrapper from '@components/ScreenWrapper';

function DisableAutoRenewSurveyPage() {
return (
<ScreenWrapper
testID={DisableAutoRenewSurveyPage.displayName}
includeSafeAreaPaddingBottom
shouldEnableMaxHeight
>
<FullPageNotFoundView shouldShow />
</ScreenWrapper>
);
}

DisableAutoRenewSurveyPage.displayName = 'DisableAutoRenewSurveyPage';

export default DisableAutoRenewSurveyPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import FeedbackSurvey from '@components/FeedbackSurvey';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';

function DisableAutoRenewSurveyPage() {
const {translate} = useLocalize();
const styles = useThemeStyles();

const handleSubmit = () => {
// TODO API call to submit feedback will be implemented in next phase
};

return (
<ScreenWrapper
testID={DisableAutoRenewSurveyPage.displayName}
includeSafeAreaPaddingBottom={false}
shouldEnablePickerAvoiding={false}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('subscription.subscriptionSettings.disableAutoRenew')}
onBackButtonPress={Navigation.goBack}
/>
<ScrollView contentContainerStyle={[styles.flexGrow1, styles.pt3]}>
<FeedbackSurvey
title={translate('subscription.subscriptionSettings.helpUsImprove')}
description={translate('subscription.subscriptionSettings.whatsMainReason')}
onSubmit={handleSubmit}
optionRowStyles={styles.flex1}
/>
</ScrollView>
</ScreenWrapper>
);
}

DisableAutoRenewSurveyPage.displayName = 'DisableAutoRenewSurveyPage';

export default DisableAutoRenewSurveyPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function SubscriptionSettings() {
return null;
}

export default SubscriptionSettings;
Loading

0 comments on commit eff0783

Please sign in to comment.