Skip to content

Commit

Permalink
Merge pull request #44763 from JKobrynski/addRequestEarlyCancelationM…
Browse files Browse the repository at this point in the history
…enuItem
  • Loading branch information
blimpich authored Jul 24, 2024
2 parents 0cde0b5 + 2731480 commit 98eb1bb
Showing 24 changed files with 386 additions and 6 deletions.
7 changes: 7 additions & 0 deletions assets/images/calendar-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
@@ -5333,6 +5333,12 @@ const CONST = {
},

EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[],

CANCELLATION_TYPE: {
MANUAL: 'manual',
AUTOMATIC: 'automatic',
NONE: 'none',
},
EMPTY_STATE_MEDIA: {
ANIMATION: 'animation',
ILLUSTRATION: 'illustration',
@@ -5401,7 +5407,8 @@ 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>;
type CancellationType = ValueOf<typeof CONST.CANCELLATION_TYPE>;

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

export default CONST;
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
@@ -385,6 +385,8 @@ const ONYXKEYS = {
/** Stores the information about the state of issuing a new card */
ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard',

NVP_PRIVATE_CANCELLATION_DETAILS: 'nvp_private_cancellationDetails',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
@@ -853,6 +855,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_BILLING_FUND_ID]: number;
[ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number;
[ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number;
[ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[];
};

type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
@@ -123,6 +123,7 @@ const ROUTES = {
SETTINGS_SUBSCRIPTION_CHANGE_BILLING_CURRENCY: 'settings/subscription/change-billing-currency',
SETTINGS_SUBSCRIPTION_CHANGE_PAYMENT_CURRENCY: 'settings/subscription/add-payment-card/change-payment-currency',
SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY: 'settings/subscription/disable-auto-renew-survey',
SETTINGS_SUBSCRIPTION_REQUEST_EARLY_CANCELLATION: 'settings/subscription/request-early-cancellation-survey',
SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode',
SETTINGS_LANGUAGE: 'settings/preferences/language',
SETTINGS_THEME: 'settings/preferences/theme',
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
@@ -111,6 +111,7 @@ const SCREENS = {
DISABLE_AUTO_RENEW_SURVEY: 'Settings_Subscription_DisableAutoRenewSurvey',
CHANGE_BILLING_CURRENCY: 'Settings_Subscription_Change_Billing_Currency',
CHANGE_PAYMENT_CURRENCY: 'Settings_Subscription_Change_Payment_Currency',
REQUEST_EARLY_CANCELLATION: 'Settings_Subscription_RequestEarlyCancellation',
},
},
SAVE_THE_WORLD: {
16 changes: 14 additions & 2 deletions src/components/FeedbackSurvey.tsx
Original file line number Diff line number Diff line change
@@ -25,6 +25,15 @@ type FeedbackSurveyProps = {

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

/** Optional text to render over the submit button */
footerText?: React.ReactNode;

/** Indicates whether note field is required */
isNoteRequired?: boolean;

/** Indicates whether a loading indicator should be shown */
isLoading?: boolean;
};

type Option = {
@@ -39,7 +48,7 @@ const OPTIONS: Option[] = [
{key: CONST.FEEDBACK_SURVEY_OPTIONS.BUSINESS_CLOSING.ID, label: CONST.FEEDBACK_SURVEY_OPTIONS.BUSINESS_CLOSING.TRANSLATION_KEY},
];

function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: FeedbackSurveyProps) {
function FeedbackSurvey({title, description, onSubmit, optionRowStyles, footerText, isNoteRequired, isLoading}: FeedbackSurveyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
@@ -55,7 +64,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac
};

const handleSubmit = () => {
if (!reason) {
if (!reason || (isNoteRequired && !note.trim())) {
setShouldShowReasonError(true);
return;
}
@@ -89,12 +98,15 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles}: Feedbac
)}
</View>
<FixedFooter>
{!!footerText && footerText}
<FormAlertWithSubmitButton
isAlertVisible={shouldShowReasonError}
onSubmit={handleSubmit}
message={translate('common.error.pleaseCompleteForm')}
buttonText={translate('common.submit')}
enabledWhenOffline
containerStyles={styles.mt3}
isLoading={isLoading}
/>
</FixedFooter>
</View>
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ import Bolt from '@assets/images/bolt.svg';
import Briefcase from '@assets/images/briefcase.svg';
import Bug from '@assets/images/bug.svg';
import Building from '@assets/images/building.svg';
import CalendarSolid from '@assets/images/calendar-solid.svg';
import Calendar from '@assets/images/calendar.svg';
import Camera from '@assets/images/camera.svg';
import CarWithKey from '@assets/images/car-with-key.svg';
@@ -372,4 +373,5 @@ export {
CheckCircle,
CheckmarkCircle,
NetSuiteSquare,
CalendarSolid,
};
43 changes: 43 additions & 0 deletions src/hooks/useCancellationType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {useEffect, useMemo, useRef, useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import type {CancellationType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

function useCancellationType(): CancellationType | undefined {
const [cancellationDetails] = useOnyx(ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS);

const [cancellationType, setCancellationType] = useState<CancellationType | undefined>();

// Store initial cancellation details array in a ref for comparison
const previousCancellationDetails = useRef(cancellationDetails);

const memoizedCancellationType = useMemo(() => {
const pendingManualCancellation = cancellationDetails?.filter((detail) => detail.cancellationType === CONST.CANCELLATION_TYPE.MANUAL).find((detail) => !detail.cancellationDate);

// There is a pending manual cancellation - return manual cancellation type
if (pendingManualCancellation) {
return CONST.CANCELLATION_TYPE.MANUAL;
}

// There are no new items in the cancellation details NVP
if (previousCancellationDetails.current?.length === cancellationDetails?.length) {
return;
}

// There is a new item in the cancellation details NVP, it has to be an automatic cancellation, as pending manual cancellations are handled above
return CONST.CANCELLATION_TYPE.AUTOMATIC;
}, [cancellationDetails]);

useEffect(() => {
if (!memoizedCancellationType) {
return;
}

setCancellationType(memoizedCancellationType);
}, [memoizedCancellationType]);

return cancellationType;
}

export default useCancellationType;
27 changes: 27 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
@@ -4225,6 +4225,33 @@ export default {
whatsMainReason: "What's the main reason you're disabling auto-renew?",
renewsOn: ({date}) => `Renews on ${date}.`,
},
requestEarlyCancellation: {
title: 'Request early cancellation',
subtitle: 'What’s the main reason you’re requesting early cancellation?',
subscriptionCanceled: {
title: 'Subscription canceled',
subtitle: 'Your annual subscription has been canceled.',
info: 'If you want to keep using your workspace(s) on a pay-per-use basis, you’re all set.',
preventFutureActivity: {
part1: 'If you’d like to prevent future activity and charges, you must ',
link: 'delete your workspace(s)',
part2: '. Note that when you delete your workspace(s), you’ll be charged for any outstanding activity that was incurred during the current calendar month.',
},
},
requestSubmitted: {
title: 'Request submitted',
subtitle: {
part1: 'Thanks for letting us know you’re interested in canceling your subscription. We’re reviewing your request and will be in touch soon via your chat with ',
link: 'Concierge',
part2: '.',
},
},
acknowledgement: {
part1: 'By requesting early cancellation, I acknowledge and agree that Expensify has no obligation to grant such request under the Expensify ',
link: 'Terms of Service',
part2: ' or other applicable services agreement between me and Expensify and that Expensify retains sole discretion with regard to granting any such request.',
},
},
},
feedbackSurvey: {
tooLimited: 'Functionality needs improvement',
27 changes: 27 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
@@ -4748,6 +4748,33 @@ export default {
whatsMainReason: '¿Cuál es la razón principal por la que deseas desactivar la auto-renovación?',
renewsOn: ({date}) => `Se renovará el ${date}.`,
},
requestEarlyCancellation: {
title: 'Solicitar cancelación anticipada',
subtitle: '¿Cuál es la razón principal por la que solicitas la cancelación anticipada?',
subscriptionCanceled: {
title: 'Suscripción cancelada',
subtitle: 'Tu suscripción anual ha sido cancelada.',
info: 'Ya puedes seguir utilizando tu(s) espacio(s) de trabajo en la modalidad de pago por uso.',
preventFutureActivity: {
part1: 'Si quieres evitar actividad y cargos futuros, debes ',
link: 'eliminar tu(s) espacio(s) de trabajo.',
part2: ' Ten en cuenta que cuando elimines tu(s) espacio(s) de trabajo, se te cobrará cualquier actividad pendienteque se haya incurrido durante el mes en curso.',
},
},
requestSubmitted: {
title: 'Solicitud enviada',
subtitle: {
part1: 'Gracias por hacernos saber que deseas cancelar tu suscripción. Estamos revisando tu solicitud y nos comunicaremos contigo en breve a través de tu chat con ',
link: 'Concierge',
part2: '.',
},
},
acknowledgement: {
part1: 'Al solicitar la cancelación anticipada, reconozco y acepto que Expensify no tiene ninguna obligación de conceder dicha solicitud en virtud de las ',
link: 'Condiciones de Servicio',
part2: ' de Expensify u otro acuerdo de servicios aplicable entre Expensify y yo, y que Expensify se reserva el derecho exclusivo a conceder dicha solicitud.',
},
},
},
feedbackSurvey: {
tooLimited: 'Hay que mejorar la funcionalidad',
8 changes: 8 additions & 0 deletions src/libs/API/parameters/CancelBillingSubscriptionParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type {FeedbackSurveyOptionID} from '@src/CONST';

type CancelBillingSubscriptionParams = {
cancellationReason: FeedbackSurveyOptionID;
cancellationNote: string;
};

export default CancelBillingSubscriptionParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
@@ -260,6 +260,7 @@ export type {default as RemoveWorkspaceReportFieldListValueParams} from './Remov
export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams';
export type {default as RequestExpensifyCardLimitIncreaseParams} from './RequestExpensifyCardLimitIncreaseParams';
export type {default as UpdateNetSuiteGenericTypeParams} from './UpdateNetSuiteGenericTypeParams';
export type {default as CancelBillingSubscriptionParams} from './CancelBillingSubscriptionParams';
export type {default as UpdateNetSuiteCustomFormIDParams} from './UpdateNetSuiteCustomFormIDParams';
export type {default as UpdateSageIntacctGenericTypeParams} from './UpdateSageIntacctGenericTypeParams';
export type {default as UpdateNetSuiteCustomersJobsParams} from './UpdateNetSuiteCustomersJobsParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
@@ -300,6 +300,7 @@ const WRITE_COMMANDS = {
UPDATE_SAGE_INTACCT_SYNC_REIMBURSEMENT_ACCOUNT_ID: 'UpdateSageIntacctSyncReimbursementAccountID',
CONNECT_POLICY_TO_NETSUITE: 'ConnectPolicyToNetSuite',
CLEAR_OUTSTANDING_BALANCE: 'ClearOutstandingBalance',
CANCEL_BILLING_SUBSCRIPTION: 'CancelBillingSubscriptionNewDot',
UPDATE_SAGE_INTACCT_ENTITY: 'UpdateSageIntacctEntity',
UPDATE_SAGE_INTACCT_BILLABLE: 'UpdateSageIntacctBillable',
UPDATE_SAGE_INTACCT_DEPARTMENT_MAPPING: 'UpdateSageIntacctDepartmentsMapping',
@@ -532,6 +533,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.MARK_AS_EXPORTED]: Parameters.MarkAsExportedParams;
[WRITE_COMMANDS.REQUEST_EXPENSIFY_CARD_LIMIT_INCREASE]: Parameters.RequestExpensifyCardLimitIncreaseParams;
[WRITE_COMMANDS.CLEAR_OUTSTANDING_BALANCE]: null;
[WRITE_COMMANDS.CANCEL_BILLING_SUBSCRIPTION]: Parameters.CancelBillingSubscriptionParams;

[WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG]: Parameters.UpdatePolicyConnectionConfigParams;
[WRITE_COMMANDS.UPDATE_MANY_POLICY_CONNECTION_CONFIGS]: Parameters.UpdateManyPolicyConnectionConfigurationsParams;
Original file line number Diff line number Diff line change
@@ -218,6 +218,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/CustomStatus/SetTimePage').default,
[SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require<ReactComponentModule>('../../../../pages/settings/Subscription/SubscriptionSize').default,
[SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: () => require<ReactComponentModule>('../../../../pages/settings/Subscription/DisableAutoRenewSurveyPage').default,
[SCREENS.SETTINGS.SUBSCRIPTION.REQUEST_EARLY_CANCELLATION]: () => require<ReactComponentModule>('../../../../pages/settings/Subscription/RequestEarlyCancellationPage').default,
[SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require<ReactComponentModule>('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage').default,
[SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require<ReactComponentModule>('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default,
[SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require<ReactComponentModule>('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default,
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> =
SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD,
SCREENS.SETTINGS.SUBSCRIPTION.SIZE,
SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY,
SCREENS.SETTINGS.SUBSCRIPTION.REQUEST_EARLY_CANCELLATION,
SCREENS.SETTINGS.SUBSCRIPTION.CHANGE_BILLING_CURRENCY,
SCREENS.SETTINGS.SUBSCRIPTION.CHANGE_PAYMENT_CURRENCY,
],
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
@@ -294,6 +294,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY,
},
[SCREENS.SETTINGS.SUBSCRIPTION.REQUEST_EARLY_CANCELLATION]: {
path: ROUTES.SETTINGS_SUBSCRIPTION_REQUEST_EARLY_CANCELLATION,
},
[SCREENS.WORKSPACE.CURRENCY]: {
path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route,
},
12 changes: 11 additions & 1 deletion src/libs/actions/Subscription.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import type {UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters';
import type {CancelBillingSubscriptionParams, UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import CONST from '@src/CONST';
import type {FeedbackSurveyOptionID, SubscriptionType} from '@src/CONST';
@@ -279,6 +279,15 @@ function clearOutstandingBalance() {
API.write(WRITE_COMMANDS.CLEAR_OUTSTANDING_BALANCE, null, onyxData);
}

function cancelBillingSubscription(cancellationReason: FeedbackSurveyOptionID, cancellationNote: string) {
const parameters: CancelBillingSubscriptionParams = {
cancellationReason,
cancellationNote,
};

API.write(WRITE_COMMANDS.CANCEL_BILLING_SUBSCRIPTION, parameters);
}

export {
openSubscriptionPage,
updateSubscriptionAutoRenew,
@@ -287,4 +296,5 @@ export {
clearUpdateSubscriptionSizeError,
updateSubscriptionType,
clearOutstandingBalance,
cancelBillingSubscription,
};
6 changes: 4 additions & 2 deletions src/pages/settings/Subscription/CardSection/CardSection.tsx
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ import TrialEndedBillingBanner from './BillingBanner/TrialEndedBillingBanner';
import TrialStartedBillingBanner from './BillingBanner/TrialStartedBillingBanner';
import CardSectionActions from './CardSectionActions';
import CardSectionDataEmpty from './CardSectionDataEmpty';
import RequestEarlyCancellationMenuItem from './RequestEarlyCancellationMenuItem';
import type {BillingStatusResult} from './utils';
import CardSectionUtils from './utils';

@@ -129,7 +130,6 @@ function CardSection() {
</View>

{isEmptyObject(defaultCard?.accountData) && <CardSectionDataEmpty />}

{billingStatus?.isRetryAvailable !== undefined && (
<Button
text={translate('subscription.cardSection.retryPaymentButton')}
@@ -148,9 +148,9 @@ function CardSection() {
wrapperStyle={styles.sectionMenuItemTopDescription}
title={translate('subscription.cardSection.viewPaymentHistory')}
titleStyle={styles.textStrong}
style={styles.mt5}
onPress={() => Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute(CONST.SEARCH.TAB.ALL))}
hoverAndPressStyle={styles.hoveredComponentBG}
style={styles.mt5}
/>
)}

@@ -165,6 +165,8 @@ function CardSection() {
onPress={() => setIsRequestRefundModalVisible(true)}
/>
)}

{privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL && <RequestEarlyCancellationMenuItem />}
</Section>

{account?.isEligibleForRefund && (
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function RequestEarlyCancellationMenuItem() {
return null;
}

export default RequestEarlyCancellationMenuItem;
Loading

0 comments on commit 98eb1bb

Please sign in to comment.