Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Subscription settings UI #42990

Merged
merged 20 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message here was left hardcoded, it needed translate function. #44075

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 @@ -3284,5 +3285,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 @@ -3791,5 +3792,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
Loading