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 size backend integration #43484

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ const ROUTES = {
SETTINGS_PRONOUNS: 'settings/profile/pronouns',
SETTINGS_PREFERENCES: 'settings/preferences',
SETTINGS_SUBSCRIPTION: 'settings/subscription',
SETTINGS_SUBSCRIPTION_SIZE: 'settings/subscription/subscription-size',
SETTINGS_SUBSCRIPTION_SIZE: {
route: 'settings/subscription/subscription-size',
getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize}` as const,
},
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',
Expand Down
1 change: 1 addition & 0 deletions src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [
SCREENS.SETTINGS.WALLET.ROOT,
SCREENS.SETTINGS.ABOUT,
SCREENS.SETTINGS.WORKSPACES,
SCREENS.SETTINGS.SUBSCRIPTION.ROOT,
SCREENS.WORKSPACE.INITIAL,
SCREENS.WORKSPACE.PROFILE,
SCREENS.WORKSPACE.CARD,
Expand Down
3 changes: 2 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3271,11 +3271,12 @@ export default {
subscriptionSize: 'Subscription size',
activeMembers: ({size}) => `${size} active members/month`,
subscriptionRenews: 'Subscription renews',
youCantDowngrade: 'You can’t downgrade during your annual subscription',
youCantDowngrade: 'You can’t downgrade during your annual subscription.',
youAlreadyCommitted: ({size, date}) =>
`You already committed to an annual subscription size of ${size} active members per month until ${date}. You can switch to a pay-per-use subscription on ${date} by disabling auto-renew.`,
error: {
size: 'Please enter a valid subscription size.',
sameSize: 'Please enter a number different than your current subscription size.',
},
},
paymentCard: {
Expand Down
3 changes: 2 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3778,11 +3778,12 @@ export default {
subscriptionSize: 'Tamaño de suscripción',
activeMembers: ({size}) => `${size} miembros activos/mes`,
subscriptionRenews: 'Renovación de la suscripción',
youCantDowngrade: 'No puedes bajar de categoría durante tu suscripción anual',
youCantDowngrade: 'No puedes bajar de categoría durante tu suscripción anual.',
youAlreadyCommitted: ({size, date}) =>
`Ya se ha comprometido a un tamaño de suscripción anual de ${size} miembros activos al mes hasta el ${date}. Puede cambiar a una suscripción de pago por uso en ${date} desactivando la auto-renovación.`,
error: {
size: 'Por favor ingrese un tamaño de suscripción valido.',
sameSize: 'Por favor, introduce un número diferente al de tu subscripción actual.',
},
},
paymentCard: {
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/UpdateSubscriptionSizeParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type UpdateSubscriptionSizeParams = {
userCount: number;
};

export default UpdateSubscriptionSizeParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,4 @@ export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscriptionTypeParams';
export type {default as SignUpUserParams} from './SignUpUserParams';
export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscriptionSizeParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ const WRITE_COMMANDS = {
MARK_AS_CASH: 'MarkAsCash',
UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize',
} as const;

type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
Expand Down Expand Up @@ -446,6 +447,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams;
[WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams;
};

const READ_COMMANDS = {
Expand Down
5 changes: 4 additions & 1 deletion src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,10 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
path: ROUTES.SETTINGS_STATUS_CLEAR_AFTER_TIME,
},
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_SIZE,
path: ROUTES.SETTINGS_SUBSCRIPTION_SIZE.route,
parse: {
canChangeSize: Number,
},
},
[SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY,
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ type SettingsNavigatorParamList = {
orderWeight: number;
tagName: string;
};
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: undefined;
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: {
canChangeSize: 0 | 1;
};
[SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD]: undefined;
[SCREENS.WORKSPACE.TAXES_SETTINGS]: {
policyID: string;
Expand Down
2 changes: 1 addition & 1 deletion src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ function isExistingTaxName(taxName: string, taxRates: TaxRates): boolean {
*/
function isValidSubscriptionSize(subscriptionSize: string): boolean {
const parsedSubscriptionSize = Number(subscriptionSize);
return !Number.isNaN(parsedSubscriptionSize) && parsedSubscriptionSize > 0 && parsedSubscriptionSize <= CONST.SUBSCRIPTION_SIZE_LIMIT;
return !Number.isNaN(parsedSubscriptionSize) && parsedSubscriptionSize > 0 && parsedSubscriptionSize <= CONST.SUBSCRIPTION_SIZE_LIMIT && Number.isInteger(parsedSubscriptionSize);
}

export {
Expand Down
60 changes: 59 additions & 1 deletion src/libs/actions/Subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import type {SubscriptionType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {OnyxData} from '@src/types/onyx/Request';

/**
* Fetches data when the user opens the SubscriptionSettingsPage
Expand Down Expand Up @@ -61,4 +62,61 @@ function updateSubscriptionType(type: SubscriptionType) {
});
}

export {openSubscriptionPage, updateSubscriptionType};
function updateSubscriptionSize(newSubscriptionSize: number, currentSubscriptionSize: number) {
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
userCount: newSubscriptionSize,
errorFields: {
userCount: null,
},
pendingFields: {
userCount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
userCount: newSubscriptionSize,
errorFields: {
userCount: null,
},
pendingFields: {
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
userCount: null,
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
userCount: currentSubscriptionSize,
pendingFields: {
userCount: null,
},
},
},
],
};

API.write(WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE, {userCount: newSubscriptionSize}, onyxData);
}

function clearUpdateSubscriptionSizeError() {
Onyx.merge(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION, {
errorFields: {
userCount: null,
},
});
}

export {openSubscriptionPage, updateSubscriptionType, updateSubscriptionSize, clearUpdateSubscriptionSizeError};
31 changes: 22 additions & 9 deletions src/pages/settings/Subscription/SubscriptionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,38 @@ function SubscriptionDetails() {
const [account] = useOnyx(ONYXKEYS.ACCOUNT);

const onOptionSelected = (option: SubscriptionType) => {
if (privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL && option === CONST.SUBSCRIPTION.TYPE.PAYPERUSE && !account?.canDowngrade) {
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_SIZE.getRoute(0));
return;
}

Subscription.updateSubscriptionType(option);
};

const onSubscriptionSizePress = () => {
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_SIZE);
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_SIZE.getRoute(1));
};

// This section is only shown when the subscription is annual
const subscriptionSizeSection: React.JSX.Element | null =
privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL ? (
<>
<MenuItemWithTopDescription
description={translate('subscription.details.subscriptionSize')}
shouldShowRightIcon
onPress={onSubscriptionSizePress}
wrapperStyle={styles.sectionMenuItemTopDescription}
style={styles.mt5}
title={`${privateSubscription?.userCount ?? ''}`}
/>
<OfflineWithFeedback
pendingAction={privateSubscription?.pendingFields?.userCount}
errors={privateSubscription?.errorFields?.userCount}
onClose={() => {
Subscription.clearUpdateSubscriptionSizeError();
}}
>
<MenuItemWithTopDescription
description={translate('subscription.details.subscriptionSize')}
shouldShowRightIcon
onPress={onSubscriptionSizePress}
wrapperStyle={styles.sectionMenuItemTopDescription}
style={styles.mt5}
title={`${privateSubscription?.userCount ?? ''}`}
/>
</OfflineWithFeedback>
{!privateSubscription?.userCount && <Text style={[styles.mt2, styles.textLabelSupporting, styles.textLineHeightNormal]}>{translate('subscription.details.headsUp')}</Text>}
</>
) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {format} from 'date-fns';
import React, {useState} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import {View} from 'react-native';
Expand All @@ -10,6 +9,7 @@ import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import {formatSubscriptionEndDate} from '@pages/settings/Subscription/utils';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -28,7 +28,7 @@ function SubscriptionSettings() {
const [autoRenew, setAutoRenew] = useState(true);
const [autoIncrease, setAutoIncrease] = useState(false);

const autoRenewalDate = privateSubscription?.endDate ? format(new Date(`${privateSubscription?.endDate}T00:00:00`), CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT) : '';
const autoRenewalDate = formatSubscriptionEndDate(privateSubscription?.endDate);

// TODO all actions will be implemented in next phase
const handleAutoRenewToggle = () => {
Expand Down
28 changes: 15 additions & 13 deletions src/pages/settings/Subscription/SubscriptionSize/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import {useOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -6,31 +7,31 @@ import useLocalize from '@hooks/useLocalize';
import useSubStep from '@hooks/useSubStep';
import type {SubStepProps} from '@hooks/useSubStep/types';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import * as Subscription from '@userActions/Subscription';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/SubscriptionSizeForm';
import Confirmation from './substeps/Confirmation';
import Size from './substeps/Size';

const bodyContent: Array<React.ComponentType<SubStepProps>> = [Size, Confirmation];

function SubscriptionSizePage() {
type SubscriptionSizePageProps = StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.SUBSCRIPTION.SIZE>;

function SubscriptionSizePage({route}: SubscriptionSizePageProps) {
const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);
const [subscriptionSizeFormDraft] = useOnyx(ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM_DRAFT);
const {translate} = useLocalize();
// TODO startFrom variable will get it's value based on ONYX data, it will be implemented in next phase (account?.canDowngrade field)
const CAN_DOWNGRADE = true;
const startFrom = CAN_DOWNGRADE ? 0 : 1;
const canChangeSubscriptionSize = !!(route.params?.canChangeSize ?? 1);
const startFrom = canChangeSubscriptionSize ? 0 : 1;

const onFinished = () => {
if (CAN_DOWNGRADE) {
// TODO this is temporary solution for the time being, API call will be implemented in next phase
// eslint-disable-next-line no-console
console.log(subscriptionSizeFormDraft);
return;
}

Subscription.updateSubscriptionSize(subscriptionSizeFormDraft ? Number(subscriptionSizeFormDraft[INPUT_IDS.SUBSCRIPTION_SIZE]) : 0, privateSubscription?.userCount ?? 0);
Navigation.goBack();
};

const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent, startFrom, onFinished});
const {componentToRender: SubStep, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent, startFrom, onFinished});

const onBackButtonPress = () => {
if (screenIndex !== 0 && startFrom === 0) {
Expand All @@ -47,13 +48,14 @@ function SubscriptionSizePage() {
includeSafeAreaPaddingBottom={false}
shouldEnablePickerAvoiding={false}
shouldEnableMaxHeight
shouldShowOfflineIndicatorInWideScreen
>
<HeaderWithBackButton
title={translate('subscription.subscriptionSize.title')}
onBackButtonPress={onBackButtonPress}
/>
<SubStep
isEditing={isEditing}
isEditing={canChangeSubscriptionSize}
onNext={nextScreen}
onMove={moveTo}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,36 @@ import FixedFooter from '@components/FixedFooter';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import {getNewSubscriptionRenewalDate} from '@pages/settings/Subscription/SubscriptionSize/utils';
import Navigation from '@navigation/Navigation';
import {formatSubscriptionEndDate, getNewSubscriptionRenewalDate} from '@pages/settings/Subscription/utils';
import * as FormActions from '@userActions/FormActions';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/SubscriptionSizeForm';

type ConfirmationProps = SubStepProps;

function Confirmation({onNext}: ConfirmationProps) {
function Confirmation({onNext, isEditing}: ConfirmationProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isOffline} = useNetwork();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);
const [subscriptionSizeFormDraft] = useOnyx(ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM_DRAFT);
const subscriptionRenewalDate = getNewSubscriptionRenewalDate();

// TODO this is temporary and will be replaced in next phase once data in ONYX is ready
// we will have to check if the amount of active members is less than the current amount of active members and if account?.canDowngrade is true - if so then we can't downgrade
const CAN_DOWNGRADE = true;
// TODO this is temporary and will be replaced in next phase once data in ONYX is ready
const SUBSCRIPTION_UNTIL = subscriptionRenewalDate;
const isTryingToIncreaseSubscriptionSize = (subscriptionSizeFormDraft ? Number(subscriptionSizeFormDraft[INPUT_IDS.SUBSCRIPTION_SIZE]) : 0) > (privateSubscription?.userCount ?? 0);
const canChangeSubscriptionSize = (account?.canDowngrade ?? false) || (isTryingToIncreaseSubscriptionSize && isEditing);
const formattedSubscriptionEndDate = formatSubscriptionEndDate(privateSubscription?.endDate);

const onClosePress = () => {
FormActions.clearDraftValues(ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM);
Navigation.goBack();
};

return (
<View style={[styles.flexGrow1]}>
{CAN_DOWNGRADE ? (
{canChangeSubscriptionSize ? (
<>
<Text style={[styles.ph5, styles.pb3]}>{translate('subscription.subscriptionSize.confirmDetails')}</Text>
<MenuItemWithTopDescription
Expand All @@ -49,20 +54,28 @@ function Confirmation({onNext}: ConfirmationProps) {
<Text style={[styles.ph5, styles.pb5, styles.textNormalThemeText]}>{translate('subscription.subscriptionSize.youCantDowngrade')}</Text>
<Text style={[styles.ph5, styles.textNormalThemeText]}>
{translate('subscription.subscriptionSize.youAlreadyCommitted', {
size: subscriptionSizeFormDraft ? subscriptionSizeFormDraft[INPUT_IDS.SUBSCRIPTION_SIZE] : 0,
date: SUBSCRIPTION_UNTIL,
size: privateSubscription?.userCount ?? 0,
date: formattedSubscriptionEndDate,
})}
</Text>
</>
)}
<FixedFooter style={[styles.mtAuto]}>
<Button
isDisabled={isOffline}
success
large
onPress={onNext}
text={translate(CAN_DOWNGRADE ? 'common.save' : 'common.close')}
/>
{canChangeSubscriptionSize ? (
<Button
success
large
onPress={onNext}
text={translate('common.save')}
/>
) : (
<Button
success
large
onPress={onClosePress}
text={translate('common.close')}
/>
)}
</FixedFooter>
</View>
);
Expand Down
Loading
Loading