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 21 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 @@ -4880,9 +4880,10 @@ 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'>>;

type SubscriptionType = ValueOf<typeof CONST.SUBSCRIPTION.TYPE>;

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

export default CONST;
22 changes: 19 additions & 3 deletions src/components/FeedbackSurvey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ 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';
import SingleOptionSelector from './SingleOptionSelector';
import Text from './Text';
import TextInput from './TextInput';

type FeedbackSurveyProps = {
/** Title of the survey */
Expand All @@ -19,14 +21,14 @@ type FeedbackSurveyProps = {
description: string;

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

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

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

Expand All @@ -44,6 +46,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac

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

const handleOptionSelect = (option: Option) => {
Expand All @@ -57,7 +60,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac
return;
}

onSubmit(reason);
onSubmit(reason.key, note);
Copy link
Contributor

Choose a reason for hiding this comment

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

@JKobrynski Whoops, I mean note.trim() 😄

};

return (
Expand All @@ -72,13 +75,26 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac
selectedOptionKey={reason?.key}
onSelectOption={handleOptionSelect}
/>
{!!reason && (
<>
<Text style={[styles.textNormalThemeText, styles.mb3]}>{translate('feedbackSurvey.additionalInfoTitle')}</Text>
<TextInput
label={translate('feedbackSurvey.additionalInfoInputLabel')}
accessibilityLabel={translate('feedbackSurvey.additionalInfoInputLabel')}
role={CONST.ROLE.PRESENTATION}
onChangeText={setNote}
value={note}
/>
</>
)}
</View>
<FixedFooter>
<FormAlertWithSubmitButton
isAlertVisible={shouldShowReasonError}
onSubmit={handleSubmit}
message="common.error.pleaseCompleteForm"
buttonText={translate('common.submit')}
enabledWhenOffline
/>
</FixedFooter>
</View>
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
6 changes: 4 additions & 2 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3293,14 +3293,16 @@ export default {
'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: 'Whats the main reason youre disabling auto-renew on your subscription?',
renewsOn: ({date}) => `Renews on ${date}`,
whatsMainReason: "What's the main reason you're disabling auto-renew?",
renewsOn: ({date}) => `Renews on ${date}.`,
},
},
feedbackSurvey: {
tooLimited: 'Functionality needs improvement',
tooExpensive: 'Too expensive',
inadequateSupport: 'Inadequate customer support',
businessClosing: 'Company closing, downsizing, or acquired',
additionalInfoTitle: 'What software are you moving to and why?',
additionalInfoInputLabel: 'Your response',
},
} satisfies TranslationBase;
6 changes: 4 additions & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3797,14 +3797,16 @@ export default {
'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}`,
whatsMainReason: '¿Cuál es la razón principal por la que deseas desactivar la auto-renovació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',
additionalInfoTitle: '¿A qué software está migrando y por qué?',
additionalInfoInputLabel: 'Tu respuesta',
},
} satisfies EnglishTranslation;
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 @@ -227,5 +227,7 @@ 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 UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams';
export type {default as UpdateSubscriptionAddNewUsersAutomaticallyParams} from './UpdateSubscriptionAddNewUsersAutomaticallyParams';
export type {default as GenerateSpotnanaTokenParams} from './GenerateSpotnanaTokenParams';
export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscriptionSizeParams';
5 changes: 5 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ const WRITE_COMMANDS = {
MARK_AS_CASH: 'MarkAsCash',
UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically',
UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize',
} as const;

Expand Down Expand Up @@ -449,6 +451,9 @@ 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_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams;
};

Expand Down
124 changes: 118 additions & 6 deletions src/libs/actions/Subscription.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import type {UpdateSubscriptionTypeParams} from '@libs/API/parameters';
import type {UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import type {SubscriptionType} from '@src/CONST';
import CONST from '@src/CONST';
import type {FeedbackSurveyOptionID, SubscriptionType} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {OnyxData} from '@src/types/onyx/Request';

Expand All @@ -22,7 +22,9 @@ function updateSubscriptionType(type: SubscriptionType) {
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
type,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
pendingFields: {
type: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errors: null,
},
},
Expand All @@ -34,7 +36,9 @@ function updateSubscriptionType(type: SubscriptionType) {
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
type,
pendingAction: null,
pendingFields: {
type: null,
},
errors: null,
},
},
Expand All @@ -46,7 +50,9 @@ function updateSubscriptionType(type: SubscriptionType) {
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
type: type === CONST.SUBSCRIPTION.TYPE.ANNUAL ? CONST.SUBSCRIPTION.TYPE.PAYPERUSE : CONST.SUBSCRIPTION.TYPE.ANNUAL,
pendingAction: null,
pendingFields: {
type: null,
},
},
},
];
Expand All @@ -62,6 +68,112 @@ function updateSubscriptionType(type: SubscriptionType) {
});
}

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
pendingFields: {
autoRenew: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errors: null,
},
},
];

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

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION,
value: {
autoRenew: !autoRenew,
pendingFields: {
autoRenew: 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,
pendingFields: {
addNewUsersAutomatically: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errors: null,
},
},
];

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

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

const parameters: UpdateSubscriptionAddNewUsersAutomaticallyParams = {
addNewUsersAutomatically,
};

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

function updateSubscriptionSize(newSubscriptionSize: number, currentSubscriptionSize: number) {
const onyxData: OnyxData = {
optimisticData: [
Expand Down Expand Up @@ -119,4 +231,4 @@ function clearUpdateSubscriptionSizeError() {
});
}

export {openSubscriptionPage, updateSubscriptionType, updateSubscriptionSize, clearUpdateSubscriptionSizeError};
export {openSubscriptionPage, updateSubscriptionAutoRenew, updateSubscriptionAddNewUsersAutomatically, updateSubscriptionSize, clearUpdateSubscriptionSizeError, updateSubscriptionType};
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, additionalNote?: string) => {
Subscription.updateSubscriptionAutoRenew(false, key, additionalNote);
Navigation.goBack();
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function SubscriptionDetails() {
<Text style={[styles.textLabelSupporting, styles.mt2]}>{translate('subscription.details.zeroCommitment')}</Text>
</View>
) : (
<OfflineWithFeedback pendingAction={privateSubscription?.pendingAction}>
<OfflineWithFeedback pendingAction={privateSubscription?.pendingFields?.type}>
<OptionsPicker
options={options}
selectedOption={privateSubscription?.type ?? CONST.SUBSCRIPTION.TYPE.ANNUAL}
Expand Down
Loading
Loading