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

[Payment card / Subscription] Integrate “Subscription settings” section with backend data #43367

Merged
merged 22 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
80b5c36
display values from API in subscription settings section
JKobrynski Jun 6, 2024
90d8701
Merge branch 'main' into integrateSubscriptionSettingsWithBackend
JKobrynski Jun 7, 2024
0214f78
Merge branch 'feat/subsription-settings-ui' into integrateSubscriptio…
JKobrynski Jun 7, 2024
2e571e9
add new api commands for subscription settings
JKobrynski Jun 7, 2024
a91bd2f
implement auto-increase annual seats setting
JKobrynski Jun 7, 2024
32bc827
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 10, 2024
b6bfeb6
start integrating UpdateSubscriptionAutoRenew API method
JKobrynski Jun 10, 2024
5919906
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 11, 2024
7718cda
integrate auto renew setting and feedback survey with API
JKobrynski Jun 11, 2024
d2e0378
add offline support to subscription settings
JKobrynski Jun 11, 2024
49d387a
apply suggested changes
JKobrynski Jun 11, 2024
51466c3
avoid overwriting values in successData
JKobrynski Jun 11, 2024
816da16
remove disableAutoRenewAdditionalNote param from UpdateSubscriptionAu…
JKobrynski Jun 12, 2024
941875a
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 13, 2024
7c23447
bring disableAutoRenewAdditionalNote back, add an input for it
JKobrynski Jun 13, 2024
972d967
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 17, 2024
2839c6a
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 17, 2024
07dffcc
apply suggested changes
JKobrynski Jun 17, 2024
fc13ff4
replace pendingAction with pendingFields
JKobrynski Jun 17, 2024
98217b1
remove comments, add enabledWhenOffline to FeedbackSurvey submit button
JKobrynski Jun 17, 2024
d1cbc24
Merge branch 'main' into implementAllToggleActions
JKobrynski Jun 18, 2024
9b805ba
apply suggested changes
JKobrynski Jun 18, 2024
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
3 changes: 2 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4850,7 +4850,8 @@ type Country = keyof typeof CONST.ALL_COUNTRIES;
type IOUType = ValueOf<typeof CONST.IOU.TYPE>;
type IOUAction = ValueOf<typeof CONST.IOU.ACTION>;
type IOURequestType = ValueOf<typeof CONST.IOU.REQUEST_TYPE>;
type FeedbackSurveyOptionID = ValueOf<Pick<ValueOf<typeof CONST.FEEDBACK_SURVEY_OPTIONS>, 'ID'>>;

export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType};
export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, FeedbackSurveyOptionID};

export default CONST;
7 changes: 4 additions & 3 deletions src/components/FeedbackSurvey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type {FeedbackSurveyOptionID} from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import FixedFooter from './FixedFooter';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
Expand All @@ -19,14 +20,14 @@ type FeedbackSurveyProps = {
description: string;

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

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

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

Expand Down Expand Up @@ -57,7 +58,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac
return;
}

onSubmit(reason);
onSubmit(reason.key);
};

return (
Expand Down
14 changes: 7 additions & 7 deletions src/components/SingleOptionSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import SelectCircle from './SelectCircle';
import Text from './Text';

type Item = {
key: string;
type Item<TKey extends string> = {
key: TKey;
label: TranslationPaths;
};

type SingleOptionSelectorProps = {
type SingleOptionSelectorProps<TKey extends string> = {
/** Array of options for the selector, key is a unique identifier, label is a localize key that will be translated and displayed */
options?: Item[];
options?: Array<Item<TKey>>;

/** Key of the option that is currently selected */
selectedOptionKey?: string;
selectedOptionKey?: TKey;

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

/** Styles for the option row element */
optionRowStyles?: StyleProp<ViewStyle>;
Expand All @@ -31,7 +31,7 @@ type SingleOptionSelectorProps = {
selectCircleStyles?: StyleProp<ViewStyle>;
};

function SingleOptionSelector({options = [], selectedOptionKey, onSelectOption = () => {}, optionRowStyles, selectCircleStyles}: SingleOptionSelectorProps) {
function SingleOptionSelector<TKey extends string>({options = [], selectedOptionKey, onSelectOption = () => {}, optionRowStyles, selectCircleStyles}: SingleOptionSelectorProps<TKey>) {
const styles = useThemeStyles();
const {translate} = useLocalize();
return (
Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3293,7 +3293,7 @@ export default {
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}`,
renewsOn: ({date}) => `Renews on ${date}.`,
},
},
feedbackSurvey: {
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3800,7 +3800,7 @@ export default {
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}`,
renewsOn: ({date}) => `Se renovará el ${date}.`,
},
},
feedbackSurvey: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type UpdateSubscriptionAddNewUsersAutomaticallyParams = {
addNewUsersAutomatically: boolean;
};

export default UpdateSubscriptionAddNewUsersAutomaticallyParams;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {FeedbackSurveyOptionID} from '@src/CONST';

type UpdateSubscriptionAutoRenewParams = {
autoRenew: boolean;
disableAutoRenewReason?: FeedbackSurveyOptionID;
disableAutoRenewAdditionalNote?: string;
};

export default UpdateSubscriptionAutoRenewParams;
2 changes: 2 additions & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,5 @@ export type {default as SendInvoiceParams} from './SendInvoiceParams';
export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
export type {default as SignUpUserParams} from './SignUpUserParams';
export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams';
export type {default as UpdateSubscriptionAddNewUsersAutomaticallyParams} from './UpdateSubscriptionAddNewUsersAutomaticallyParams';
5 changes: 5 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ const WRITE_COMMANDS = {
PAY_INVOICE: 'PayInvoice',
MARK_AS_CASH: 'MarkAsCash',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically',
} as const;

type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
Expand Down Expand Up @@ -444,6 +446,9 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams;
[WRITE_COMMANDS.MARK_AS_CASH]: Parameters.MarkAsCashParams;
[WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;

[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams;
};

const READ_COMMANDS = {
Expand Down
107 changes: 102 additions & 5 deletions src/libs/actions/Subscription.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import {READ_COMMANDS} from '@libs/API/types';
import type {UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import CONST from '@src/CONST';
import type {FeedbackSurveyOptionID} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

/**
* Fetches data when the user opens the SubscriptionSettingsPage
Expand All @@ -8,7 +14,98 @@ function openSubscriptionPage() {
API.read(READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE, null);
}

export {
// eslint-disable-next-line import/prefer-default-export
openSubscriptionPage,
};
function updateSubscriptionAutoRenew(autoRenew: boolean, disableAutoRenewReason?: FeedbackSurveyOptionID, disableAutoRenewAdditionalNote?: string) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
autoRenew,
blimpich marked this conversation as resolved.
Show resolved Hide resolved
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
errors: null,
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
pendingAction: null,
errors: null,
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
autoRenew: !autoRenew,
pendingAction: null,
},
},
];

const parameters: UpdateSubscriptionAutoRenewParams = {
autoRenew,
disableAutoRenewReason,
disableAutoRenewAdditionalNote,
};

API.write(WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW, parameters, {
optimisticData,
successData,
failureData,
});
}

function updateSubscriptionAddNewUsersAutomatically(addNewUsersAutomatically: boolean) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
addNewUsersAutomatically,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
errors: null,
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
pendingAction: null,
errors: null,
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
addNewUsersAutomatically: !addNewUsersAutomatically,
pendingAction: null,
},
},
];

const parameters: UpdateSubscriptionAddNewUsersAutomaticallyParams = {
addNewUsersAutomatically,
};

API.write(WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY, parameters, {
optimisticData,
successData,
failureData,
});
}

export {openSubscriptionPage, updateSubscriptionAutoRenew, updateSubscriptionAddNewUsersAutomatically};
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as Subscription from '@userActions/Subscription';
import type {FeedbackSurveyOptionID} from '@src/CONST';

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

const handleSubmit = () => {
// TODO API call to submit feedback will be implemented in next phase
const handleSubmit = (key: FeedbackSurveyOptionID) => {
Subscription.updateSubscriptionAutoRenew(false, key);
Navigation.goBack();
};

return (
Expand Down
59 changes: 30 additions & 29 deletions src/pages/settings/Subscription/SubscriptionSettings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {format} from 'date-fns';
import React, {useState} from 'react';
import React from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Section from '@components/Section';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -11,6 +12,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
import * as Subscription from '@userActions/Subscription';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -24,24 +26,21 @@ function SubscriptionSettings() {

const isCollect = subscriptionPlan === CONST.POLICY.TYPE.TEAM;

// TODO these default state values will come from API in next phase
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) : '';

// TODO all actions will be implemented in next phase
const handleAutoRenewToggle = () => {
if (!autoRenew) {
// TODO make API call to enable auto renew here
setAutoRenew(true);
if (!privateSubscription?.autoRenew) {
Subscription.updateSubscriptionAutoRenew(true);
return;
}
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY);
};
const handleAutoIncreaseToggle = () => {
// TODO make API call to toggle auto increase here
setAutoIncrease(!autoIncrease);
if (privateSubscription?.addNewUsersAutomatically === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is enough

const handleAutoIncreaseToggle = () => { 
 Subscription.updateSubscriptionAddNewUsersAutomatically(!privateSubscription?.addNewUsersAutomatically);
};

Subscription.updateSubscriptionAddNewUsersAutomatically(true);
} else {
Subscription.updateSubscriptionAddNewUsersAutomatically(!privateSubscription.addNewUsersAutomatically);
}
};

if (privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.PAYPERUSE) {
Expand All @@ -66,24 +65,26 @@ function SubscriptionSettings() {
titleStyles={styles.textStrong}
isCentralPane
>
<View style={styles.mt5}>
<ToggleSettingOptionRow
title={translate('subscription.subscriptionSettings.autoRenew')}
switchAccessibilityLabel={translate('subscription.subscriptionSettings.autoRenew')}
onToggle={handleAutoRenewToggle}
isActive={autoRenew}
/>
<Text style={[styles.mutedTextLabel, styles.mt2]}>{translate('subscription.subscriptionSettings.renewsOn', {date: autoRenewalDate})}</Text>
</View>
<View style={styles.mt3}>
<ToggleSettingOptionRow
customTitle={customTitle}
switchAccessibilityLabel={translate('subscription.subscriptionSettings.autoRenew')}
onToggle={handleAutoIncreaseToggle}
isActive={autoIncrease}
/>
<Text style={[styles.mutedTextLabel, styles.mt2]}>{translate('subscription.subscriptionSettings.automaticallyIncrease')}</Text>
</View>
<OfflineWithFeedback pendingAction={privateSubscription?.pendingAction}>
<View style={styles.mt5}>
<ToggleSettingOptionRow
title={translate('subscription.subscriptionSettings.autoRenew')}
switchAccessibilityLabel={translate('subscription.subscriptionSettings.autoRenew')}
onToggle={handleAutoRenewToggle}
isActive={privateSubscription?.autoRenew ?? false}
/>
{!!autoRenewalDate && <Text style={[styles.mutedTextLabel, styles.mt2]}>{translate('subscription.subscriptionSettings.renewsOn', {date: autoRenewalDate})}</Text>}
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add

<OfflineWithFeedback pendingAction={privateSubscription?.pendingFields?.addNewUsersAutomatically}>

</View>
<View style={styles.mt3}>
<ToggleSettingOptionRow
customTitle={customTitle}
switchAccessibilityLabel={translate('subscription.subscriptionSettings.autoRenew')}
onToggle={handleAutoIncreaseToggle}
isActive={privateSubscription?.addNewUsersAutomatically ?? false}
/>
<Text style={[styles.mutedTextLabel, styles.mt2]}>{translate('subscription.subscriptionSettings.automaticallyIncrease')}</Text>
</View>
</OfflineWithFeedback>
</Section>
);
}
Expand Down
8 changes: 6 additions & 2 deletions src/types/onyx/PrivateSubscription.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type * as OnyxCommon from './OnyxCommon';

/** Model of private subscription */
type PrivateSubscription = {
type PrivateSubscription = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** "auto increase annual seats" setting */
addNewUsersAutomatically: boolean;

Expand All @@ -26,6 +27,9 @@ type PrivateSubscription = {

/** Subscription size */
userCount?: number;
};

/** An error message */
errors?: OnyxCommon.Errors;
}>;

export default PrivateSubscription;
Loading