mirror of
https://github.com/mozilla/fxa.git
synced 2025-12-13 20:36:41 +01:00
polish(payments-next): Adjust styling and server action for terms
Because: * A few code style and css-style tweaks were needed as a follow to the terms page release This commit: * addresses some of the outcomes from style conversations Closes #N/A
This commit is contained in:
@@ -2,9 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Breadcrumbs, Header } from '@fxa/payments/ui';
|
||||
import { Header } from '@fxa/payments/ui';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -16,19 +15,13 @@ export default async function ChurnLayout({
|
||||
const session = await auth();
|
||||
|
||||
return (
|
||||
<div className="min-h-[calc(100vh_-_4rem)] bg-white tablet:bg-grey-10 flex flex-col justify-start">
|
||||
<>
|
||||
<Header
|
||||
auth={{
|
||||
user: session?.user,
|
||||
}}
|
||||
/>
|
||||
<Breadcrumbs
|
||||
contentServerUrl={config.contentServerUrl}
|
||||
paymentsNextUrl={config.paymentsNextHostedUrl}
|
||||
/>
|
||||
<div className="flex flex-col tablet:pt-20 items-center flex-1">
|
||||
<div className="flex justify-center max-w-lg">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
<>{children}</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
loyalty-discount-terms-heading = Terms and Restrictions
|
||||
loyalty-discount-terms-heading = Terms and restrictions
|
||||
loyalty-discount-terms-support = Contact Support
|
||||
loyalty-discount-terms-support-aria = Contact Support
|
||||
# $productName (String) - The name of the product to create subscription, e.g. Mozilla VPN
|
||||
loyalty-discount-terms-contact-support-product-aria = Contact Support for { $productName }
|
||||
not-found-page-title-terms = Page not found
|
||||
not-found-page-description-terms = The page you’re looking for does not exist.
|
||||
not-found-page-button-terms-manage-subscriptions = Manage subscriptions
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { headers } from 'next/headers';
|
||||
import { PageNotFound } from '@fxa/payments/ui';
|
||||
import { getApp } from '@fxa/payments/ui/server';
|
||||
|
||||
export enum PaymentsPage {
|
||||
Subscriptions = 'subscriptions',
|
||||
}
|
||||
|
||||
export default function NotFound() {
|
||||
const acceptLanguage = headers().get('accept-language');
|
||||
const l10n = getApp().getL10n(acceptLanguage);
|
||||
return (
|
||||
<PageNotFound
|
||||
header={l10n.getString('not-found-page-title-terms', 'Page not found')}
|
||||
description={l10n.getString(
|
||||
'not-found-page-description-terms',
|
||||
'The page you’re looking for does not exist.'
|
||||
)}
|
||||
button={l10n.getString(
|
||||
'not-found-page-button-terms-manage-subscriptions',
|
||||
'Manage subscriptions'
|
||||
)}
|
||||
paymentsPage={PaymentsPage.Subscriptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { getApp } from '@fxa/payments/ui/server';
|
||||
import { headers } from 'next/headers';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { SubplatInterval } from '@fxa/payments/customer';
|
||||
import { BaseButton, ButtonVariant } from '@fxa/payments/ui';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default async function ChurnTerms({
|
||||
params,
|
||||
@@ -37,45 +37,61 @@ export default async function ChurnTerms({
|
||||
selectedLanguage: locale,
|
||||
});
|
||||
|
||||
const content = churnIntervention.churnInterventions.at(0);
|
||||
|
||||
if (
|
||||
!content ||
|
||||
!content.termsHeading ||
|
||||
!Array.isArray(content.termsDetails) ||
|
||||
content.termsDetails.length === 0
|
||||
) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col tablet:bg-white p-8 tablet:rounded-lg tablet:shadow-lg">
|
||||
<h1 className="font-bold text-lg my-1">
|
||||
{l10n.getString(
|
||||
'loyalty-discount-terms-heading',
|
||||
'Terms and Restrictions'
|
||||
)}
|
||||
</h1>
|
||||
<h1 className="font-bold text-lg my-1">
|
||||
{`${churnIntervention.churnInterventions[0].termsHeading}`}
|
||||
</h1>
|
||||
<ul className="list-disc ml-5 my-2 marker:text-xs text-sm font-light [&_li]:leading-5">
|
||||
{churnIntervention.churnInterventions[0].termsDetails.map(
|
||||
(term, index) => (
|
||||
<li key={index}>{term}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
<div className="flex justify-start">
|
||||
<LinkExternal
|
||||
href={`${churnIntervention.churnInterventions[0].supportUrl}${searchParamsString}`}
|
||||
aria-label={l10n.getString(
|
||||
'loyalty-discount-terms-support-aria',
|
||||
'Contact Support'
|
||||
)}
|
||||
<section
|
||||
className="flex tablet:items-center justify-center min-h-[calc(100vh_-_4rem)] tablet:min-h-[calc(100vh_-_5rem)]"
|
||||
aria-labelledby="loyalty-discount-terms"
|
||||
>
|
||||
<div className="max-w-xl flex flex-col p-6 pt-10 tablet:bg-white tablet:border tablet:border-grey-200 tablet:opacity-100 tablet:p-8 tablet:rounded-xl tablet:shadow-[0_0px_10px_rgba(0,0,0,0.08)]">
|
||||
<h1
|
||||
id="loyalty-discount-terms"
|
||||
className="font-semibold text-xl leading-8"
|
||||
>
|
||||
<BaseButton
|
||||
variant={ButtonVariant.SubscriptionManagementSecondary}
|
||||
className="w-40 mt-4"
|
||||
{l10n.getString(
|
||||
'loyalty-discount-terms-heading',
|
||||
'Terms and restrictions'
|
||||
)}
|
||||
</h1>
|
||||
<h2 className="font-semibold text-xl leading-8">
|
||||
{`${content.termsHeading}`}
|
||||
</h2>
|
||||
<div className="mt-3 mx-6 mb-6 tablet:mx-10">
|
||||
<ul className="font-light leading-6 list-disc marker:text-sm marker:leading-normal">
|
||||
{content.termsDetails.map((term, index) => (
|
||||
<li key={index}>{term}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<LinkExternal
|
||||
className="border box-border font-header rounded text-center py-2 px-5 border-grey-200 w-auto bg-grey-10 font-semibold hover:bg-grey-50 text-grey-700"
|
||||
href={`${content.supportUrl}${searchParamsString}`}
|
||||
aria-label={l10n.getString(
|
||||
'loyalty-discount-terms-contact-support-product-aria',
|
||||
{
|
||||
productName: content.productName,
|
||||
},
|
||||
`Contact support for ${content.productName}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{l10n.getString(
|
||||
'loyalty-discount-terms-support',
|
||||
'Contact Support'
|
||||
)}
|
||||
</span>
|
||||
</BaseButton>
|
||||
</LinkExternal>
|
||||
{l10n.getString(
|
||||
'loyalty-discount-terms-support',
|
||||
'Contact Support'
|
||||
)}
|
||||
</LinkExternal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
26
libs/payments/management/src/lib/churn-intervention.error.ts
Normal file
26
libs/payments/management/src/lib/churn-intervention.error.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { BaseError } from '@fxa/shared/error';
|
||||
|
||||
/**
|
||||
* ChurnInterventionError is not intended for direct use, except for type-checking errors.
|
||||
* When throwing a new ChurnInterventionError, create a unique extension of the class.
|
||||
*/
|
||||
export class ChurnInterventionError extends BaseError {
|
||||
constructor(message: string, info: Record<string, any>, cause?: Error) {
|
||||
super(message, { info, cause });
|
||||
this.name = 'ChurnInterventionError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ChurnInterventionProductIdentifierMissingError extends ChurnInterventionError {
|
||||
constructor() {
|
||||
super(
|
||||
'Either stripeProductId or offeringApiIdentifier must be provided',
|
||||
{}
|
||||
);
|
||||
this.name = 'ChurnInterventionProductIdentifierMissingError';
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@
|
||||
import { Inject, Injectable, Logger, type LoggerService } from '@nestjs/common';
|
||||
import { ChurnInterventionManager } from '@fxa/payments/cart';
|
||||
import {
|
||||
ChurnInterventionByProductIdResultUtil,
|
||||
ProductConfigurationManager,
|
||||
} from '@fxa/shared/cms';
|
||||
import { SubscriptionManagementService } from './subscriptionManagement.service';
|
||||
import {
|
||||
StatsDService,
|
||||
} from '@fxa/shared/metrics/statsd';
|
||||
import { StatsDService } from '@fxa/shared/metrics/statsd';
|
||||
import { StatsD } from 'hot-shots';
|
||||
import { SubplatInterval } from '@fxa/payments/customer';
|
||||
import { ChurnInterventionProductIdentifierMissingError } from './churn-intervention.error';
|
||||
|
||||
@Injectable()
|
||||
export class ChurnInterventionService {
|
||||
@@ -20,10 +21,9 @@ export class ChurnInterventionService {
|
||||
private churnInterventionManager: ChurnInterventionManager,
|
||||
private subscriptionManagementService: SubscriptionManagementService,
|
||||
@Inject(StatsDService) private statsd: StatsD,
|
||||
@Inject(Logger) private log: LoggerService,
|
||||
@Inject(Logger) private log: LoggerService
|
||||
) {}
|
||||
|
||||
|
||||
async getChurnInterventionForCustomerId(
|
||||
customerId: string,
|
||||
churnInterventionId: string
|
||||
@@ -34,11 +34,47 @@ export class ChurnInterventionService {
|
||||
);
|
||||
}
|
||||
|
||||
async getChurnInterventionForProduct(
|
||||
interval: SubplatInterval,
|
||||
churnType: 'cancel' | 'stay_subscribed',
|
||||
stripeProductId?: string,
|
||||
offeringApiIdentifier?: string,
|
||||
acceptLanguage?: string,
|
||||
selectedLanguage?: string
|
||||
) {
|
||||
let util: ChurnInterventionByProductIdResultUtil;
|
||||
if (stripeProductId) {
|
||||
util = await this.productConfigurationManager.getChurnIntervention(
|
||||
interval,
|
||||
churnType,
|
||||
stripeProductId,
|
||||
null,
|
||||
acceptLanguage,
|
||||
selectedLanguage
|
||||
);
|
||||
} else if (offeringApiIdentifier) {
|
||||
util = await this.productConfigurationManager.getChurnIntervention(
|
||||
interval,
|
||||
churnType,
|
||||
null,
|
||||
offeringApiIdentifier,
|
||||
acceptLanguage,
|
||||
selectedLanguage
|
||||
);
|
||||
} else {
|
||||
throw new ChurnInterventionProductIdentifierMissingError();
|
||||
}
|
||||
|
||||
return {
|
||||
churnInterventions: util.getTransformedChurnInterventionByProductId(),
|
||||
};
|
||||
}
|
||||
|
||||
async determineStaySubscribedEligibility(
|
||||
uid: string,
|
||||
subscriptionId: string,
|
||||
acceptLanguage?: string | null,
|
||||
selectedLanguage?: string,
|
||||
selectedLanguage?: string
|
||||
) {
|
||||
try {
|
||||
const cmsChurnResult =
|
||||
@@ -49,7 +85,8 @@ export class ChurnInterventionService {
|
||||
selectedLanguage
|
||||
);
|
||||
|
||||
const cmsChurnInterventionEntries = cmsChurnResult.getTransformedChurnInterventionByProductId();
|
||||
const cmsChurnInterventionEntries =
|
||||
cmsChurnResult.getTransformedChurnInterventionByProductId();
|
||||
if (!cmsChurnInterventionEntries.length) {
|
||||
this.statsd.increment('stay_subscribed_eligibility', {
|
||||
eligibility: 'ineligible',
|
||||
@@ -59,16 +96,20 @@ export class ChurnInterventionService {
|
||||
isEligible: false,
|
||||
reason: 'no_churn_intervention_found',
|
||||
cmsChurnInterventionEntry: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const cmsChurnInterventionEntry = cmsChurnInterventionEntries[0];
|
||||
const redemptionCount = await this.churnInterventionManager.getRedemptionCountForUid(
|
||||
uid,
|
||||
cmsChurnInterventionEntry.churnInterventionId
|
||||
);
|
||||
const redemptionCount =
|
||||
await this.churnInterventionManager.getRedemptionCountForUid(
|
||||
uid,
|
||||
cmsChurnInterventionEntry.churnInterventionId
|
||||
);
|
||||
|
||||
if (cmsChurnInterventionEntry.redemptionLimit && redemptionCount >= cmsChurnInterventionEntry.redemptionLimit) {
|
||||
if (
|
||||
cmsChurnInterventionEntry.redemptionLimit &&
|
||||
redemptionCount >= cmsChurnInterventionEntry.redemptionLimit
|
||||
) {
|
||||
this.statsd.increment('stay_subscribed_eligibility', {
|
||||
eligibility: 'ineligible',
|
||||
reason: 'discount_already_applied',
|
||||
@@ -77,13 +118,14 @@ export class ChurnInterventionService {
|
||||
isEligible: false,
|
||||
reason: 'discount_already_applied',
|
||||
cmsChurnInterventionEntry: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const subscriptionStatus = await this.subscriptionManagementService.getSubscriptionStatus(
|
||||
uid,
|
||||
subscriptionId
|
||||
);
|
||||
const subscriptionStatus =
|
||||
await this.subscriptionManagementService.getSubscriptionStatus(
|
||||
uid,
|
||||
subscriptionId
|
||||
);
|
||||
if (!subscriptionStatus.active) {
|
||||
this.statsd.increment('stay_subscribed_eligibility', {
|
||||
eligibility: 'ineligible',
|
||||
@@ -93,7 +135,7 @@ export class ChurnInterventionService {
|
||||
isEligible: false,
|
||||
reason: 'subscription_not_active',
|
||||
cmsChurnInterventionEntry: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
if (!subscriptionStatus.cancelAtPeriodEnd) {
|
||||
this.statsd.increment('stay_subscribed_eligibility', {
|
||||
@@ -114,14 +156,14 @@ export class ChurnInterventionService {
|
||||
isEligible: true,
|
||||
reason: 'eligible',
|
||||
cmsChurnInterventionEntry,
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
this.log.error(error);
|
||||
return {
|
||||
isEligible: false,
|
||||
reason: 'general_error',
|
||||
cmsChurnInterventionEntry: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +171,7 @@ export class ChurnInterventionService {
|
||||
uid: string,
|
||||
subscriptionId: string,
|
||||
acceptLanguage?: string | null,
|
||||
selectedLanguage?: string,
|
||||
selectedLanguage?: string
|
||||
) {
|
||||
const eligibilityResult = await this.determineStaySubscribedEligibility(
|
||||
uid,
|
||||
@@ -138,7 +180,10 @@ export class ChurnInterventionService {
|
||||
selectedLanguage
|
||||
);
|
||||
|
||||
if (!eligibilityResult.isEligible || !eligibilityResult.cmsChurnInterventionEntry) {
|
||||
if (
|
||||
!eligibilityResult.isEligible ||
|
||||
!eligibilityResult.cmsChurnInterventionEntry
|
||||
) {
|
||||
return {
|
||||
redeemed: false,
|
||||
reason: eligibilityResult.reason,
|
||||
@@ -148,18 +193,26 @@ export class ChurnInterventionService {
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedSubscription = await this.subscriptionManagementService.applyStripeCouponToSubscription({
|
||||
uid,
|
||||
subscriptionId,
|
||||
stripeCouponId: eligibilityResult.cmsChurnInterventionEntry.stripeCouponId,
|
||||
setCancelAtPeriodEnd: true,
|
||||
});
|
||||
if (!updatedSubscription || updatedSubscription.cancel_at_period_end !== true) {
|
||||
const updatedSubscription =
|
||||
await this.subscriptionManagementService.applyStripeCouponToSubscription(
|
||||
{
|
||||
uid,
|
||||
subscriptionId,
|
||||
stripeCouponId:
|
||||
eligibilityResult.cmsChurnInterventionEntry.stripeCouponId,
|
||||
setCancelAtPeriodEnd: true,
|
||||
}
|
||||
);
|
||||
if (
|
||||
!updatedSubscription ||
|
||||
updatedSubscription.cancel_at_period_end !== true
|
||||
) {
|
||||
return {
|
||||
redeemed: false,
|
||||
reason: 'stripe_subscription_update_failed',
|
||||
updatedChurnInterventionEntryData: null,
|
||||
cmsChurnInterventionEntry: eligibilityResult.cmsChurnInterventionEntry,
|
||||
cmsChurnInterventionEntry:
|
||||
eligibilityResult.cmsChurnInterventionEntry,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,15 @@ import {
|
||||
} from '@fxa/payments/cart';
|
||||
import { ContentServerManager } from '@fxa/payments/content-server';
|
||||
import { CurrencyManager } from '@fxa/payments/currency';
|
||||
import { SubscriptionManagementService, ChurnInterventionService } from '@fxa/payments/management';
|
||||
import {
|
||||
SubscriptionManagementService,
|
||||
ChurnInterventionService,
|
||||
} from '@fxa/payments/management';
|
||||
import {
|
||||
CheckoutTokenManager,
|
||||
PaypalBillingAgreementManager,
|
||||
} from '@fxa/payments/paypal';
|
||||
import {
|
||||
ChurnInterventionByProductIdResultUtil,
|
||||
ProductConfigError,
|
||||
ProductConfigurationManager,
|
||||
} from '@fxa/shared/cms';
|
||||
@@ -279,15 +281,12 @@ export class NextJSActionsService {
|
||||
args.uid,
|
||||
args.subscriptionId,
|
||||
args.acceptLanguage,
|
||||
args.selectedLanguage,
|
||||
args.selectedLanguage
|
||||
);
|
||||
}
|
||||
|
||||
@SanitizeExceptions()
|
||||
@NextIOValidator(
|
||||
RedeemChurnCouponActionArgs,
|
||||
RedeemChurnCouponActionResult
|
||||
)
|
||||
@NextIOValidator(RedeemChurnCouponActionArgs, RedeemChurnCouponActionResult)
|
||||
@WithTypeCachableAsyncLocalStorage()
|
||||
@CaptureTimingWithStatsD()
|
||||
async redeemChurnCoupon(args: {
|
||||
@@ -300,7 +299,7 @@ export class NextJSActionsService {
|
||||
args.uid,
|
||||
args.subscriptionId,
|
||||
args.acceptLanguage,
|
||||
args.selectedLanguage,
|
||||
args.selectedLanguage
|
||||
);
|
||||
}
|
||||
|
||||
@@ -903,36 +902,13 @@ export class NextJSActionsService {
|
||||
acceptLanguage?: string;
|
||||
selectedLanguage?: string;
|
||||
}) {
|
||||
let util: ChurnInterventionByProductIdResultUtil;
|
||||
if (args.stripeProductId) {
|
||||
util = await this.productConfigurationManager.getChurnIntervention(
|
||||
args.interval,
|
||||
args.churnType,
|
||||
args.stripeProductId,
|
||||
null,
|
||||
args.acceptLanguage,
|
||||
args.selectedLanguage
|
||||
);
|
||||
} else if (args.offeringApiIdentifier) {
|
||||
util = await this.productConfigurationManager.getChurnIntervention(
|
||||
args.interval,
|
||||
args.churnType,
|
||||
null,
|
||||
args.offeringApiIdentifier,
|
||||
args.acceptLanguage,
|
||||
args.selectedLanguage
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Either stripeProductId or offeringApiIdentifier must be provided'
|
||||
);
|
||||
}
|
||||
|
||||
const churnInterventions =
|
||||
util.getTransformedChurnInterventionByProductId();
|
||||
|
||||
return {
|
||||
churnInterventions,
|
||||
};
|
||||
return await this.churnInterventionService.getChurnInterventionForProduct(
|
||||
args.interval,
|
||||
args.churnType,
|
||||
args.stripeProductId,
|
||||
args.offeringApiIdentifier,
|
||||
args.acceptLanguage,
|
||||
args.selectedLanguage
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
6
libs/shared/cms/src/__generated__/gql.ts
generated
6
libs/shared/cms/src/__generated__/gql.ts
generated
@@ -16,7 +16,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
|
||||
type Documents = {
|
||||
"\n query CancelInterstitialOffer(\n $offeringApiIdentifier: String!\n $currentInterval: String!\n $upgradeInterval: String!\n $locale: String!\n ) {\n cancelInterstitialOffers(\n filters: {\n offeringApiIdentifier: { eq: $offeringApiIdentifier }\n currentInterval: { eq: $currentInterval }\n upgradeInterval: { eq: $upgradeInterval }\n }\n ) {\n offeringApiIdentifier\n currentInterval\n upgradeInterval\n advertisedSavings\n ctaMessage\n modalHeading1\n modalHeading2\n modalMessage\n productPageUrl\n upgradeButtonLabel\n upgradeButtonUrl\n localizations(filters: { locale: { eq: $locale } }) {\n ctaMessage\n modalHeading1\n modalHeading2\n modalMessage\n productPageUrl\n upgradeButtonLabel\n upgradeButtonUrl\n }\n offering {\n stripeProductId\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n }\n }\n }\n": typeof types.CancelInterstitialOfferDocument,
|
||||
"\n query CapabilityServiceByPlanIds($stripePlanIds: [String]!) {\n purchases(\n filters: {\n or: [\n { stripePlanChoices: { stripePlanChoice: { in: $stripePlanIds } } }\n {\n offering: {\n stripeLegacyPlans: { stripeLegacyPlan: { in: $stripePlanIds } }\n }\n }\n ]\n }\n pagination: { limit: 200 }\n ) {\n stripePlanChoices {\n stripePlanChoice\n }\n offering {\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n capabilities {\n slug\n services {\n oauthClientId\n }\n }\n }\n }\n }\n": typeof types.CapabilityServiceByPlanIdsDocument,
|
||||
"\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n": typeof types.ChurnInterventionByProductIdDocument,
|
||||
"\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n": typeof types.ChurnInterventionByProductIdDocument,
|
||||
"\n query EligibilityContentByOffering($apiIdentifier: String!) {\n offerings(\n filters: { apiIdentifier: { eq: $apiIdentifier } }\n pagination: { limit: 200 }\n ) {\n apiIdentifier\n stripeProductId\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n }\n subGroups {\n groupName\n offerings {\n apiIdentifier\n stripeProductId\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n }\n }\n }\n }\n }\n": typeof types.EligibilityContentByOfferingDocument,
|
||||
"\n query EligibilityContentByPlanIds($stripePlanIds: [String]!) {\n purchases(\n filters: {\n or: [\n { stripePlanChoices: { stripePlanChoice: { in: $stripePlanIds } } }\n {\n offering: {\n stripeLegacyPlans: { stripeLegacyPlan: { in: $stripePlanIds } }\n }\n }\n ]\n }\n pagination: { limit: 200 }\n ) {\n stripePlanChoices {\n stripePlanChoice\n }\n offering {\n apiIdentifier\n stripeProductId\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n countries\n subGroups {\n groupName\n offerings {\n apiIdentifier\n stripeProductId\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n countries\n }\n }\n }\n }\n }\n": typeof types.EligibilityContentByPlanIdsDocument,
|
||||
"\n query IapOfferingsByStoreIDs($locale: String!, $storeIDs: [String!]!) {\n iaps(filters: { storeID: { in: $storeIDs } }) {\n storeID\n interval\n offering {\n apiIdentifier\n commonContent {\n supportUrl\n localizations(filters: { locale: { eq: $locale } }) {\n supportUrl\n }\n }\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n subGroups {\n groupName\n offerings {\n apiIdentifier\n }\n }\n }\n }\n }\n": typeof types.IapOfferingsByStoreIDsDocument,
|
||||
@@ -31,7 +31,7 @@ type Documents = {
|
||||
const documents: Documents = {
|
||||
"\n query CancelInterstitialOffer(\n $offeringApiIdentifier: String!\n $currentInterval: String!\n $upgradeInterval: String!\n $locale: String!\n ) {\n cancelInterstitialOffers(\n filters: {\n offeringApiIdentifier: { eq: $offeringApiIdentifier }\n currentInterval: { eq: $currentInterval }\n upgradeInterval: { eq: $upgradeInterval }\n }\n ) {\n offeringApiIdentifier\n currentInterval\n upgradeInterval\n advertisedSavings\n ctaMessage\n modalHeading1\n modalHeading2\n modalMessage\n productPageUrl\n upgradeButtonLabel\n upgradeButtonUrl\n localizations(filters: { locale: { eq: $locale } }) {\n ctaMessage\n modalHeading1\n modalHeading2\n modalMessage\n productPageUrl\n upgradeButtonLabel\n upgradeButtonUrl\n }\n offering {\n stripeProductId\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n }\n }\n }\n": types.CancelInterstitialOfferDocument,
|
||||
"\n query CapabilityServiceByPlanIds($stripePlanIds: [String]!) {\n purchases(\n filters: {\n or: [\n { stripePlanChoices: { stripePlanChoice: { in: $stripePlanIds } } }\n {\n offering: {\n stripeLegacyPlans: { stripeLegacyPlan: { in: $stripePlanIds } }\n }\n }\n ]\n }\n pagination: { limit: 200 }\n ) {\n stripePlanChoices {\n stripePlanChoice\n }\n offering {\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n capabilities {\n slug\n services {\n oauthClientId\n }\n }\n }\n }\n }\n": types.CapabilityServiceByPlanIdsDocument,
|
||||
"\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n": types.ChurnInterventionByProductIdDocument,
|
||||
"\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n": types.ChurnInterventionByProductIdDocument,
|
||||
"\n query EligibilityContentByOffering($apiIdentifier: String!) {\n offerings(\n filters: { apiIdentifier: { eq: $apiIdentifier } }\n pagination: { limit: 200 }\n ) {\n apiIdentifier\n stripeProductId\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n }\n subGroups {\n groupName\n offerings {\n apiIdentifier\n stripeProductId\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n }\n }\n }\n }\n }\n": types.EligibilityContentByOfferingDocument,
|
||||
"\n query EligibilityContentByPlanIds($stripePlanIds: [String]!) {\n purchases(\n filters: {\n or: [\n { stripePlanChoices: { stripePlanChoice: { in: $stripePlanIds } } }\n {\n offering: {\n stripeLegacyPlans: { stripeLegacyPlan: { in: $stripePlanIds } }\n }\n }\n ]\n }\n pagination: { limit: 200 }\n ) {\n stripePlanChoices {\n stripePlanChoice\n }\n offering {\n apiIdentifier\n stripeProductId\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n countries\n subGroups {\n groupName\n offerings {\n apiIdentifier\n stripeProductId\n stripeLegacyPlans(pagination: { limit: 200 }) {\n stripeLegacyPlan\n }\n countries\n }\n }\n }\n }\n }\n": types.EligibilityContentByPlanIdsDocument,
|
||||
"\n query IapOfferingsByStoreIDs($locale: String!, $storeIDs: [String!]!) {\n iaps(filters: { storeID: { in: $storeIDs } }) {\n storeID\n interval\n offering {\n apiIdentifier\n commonContent {\n supportUrl\n localizations(filters: { locale: { eq: $locale } }) {\n supportUrl\n }\n }\n defaultPurchase {\n stripePlanChoices {\n stripePlanChoice\n }\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n subGroups {\n groupName\n offerings {\n apiIdentifier\n }\n }\n }\n }\n }\n": types.IapOfferingsByStoreIDsDocument,
|
||||
@@ -69,7 +69,7 @@ export function graphql(source: "\n query CapabilityServiceByPlanIds($stripePla
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n"): (typeof documents)["\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n"): (typeof documents)["\n query ChurnInterventionByProductId(\n $offeringApiIdentifier: String\n $stripeProductId: String\n $interval: String!\n $locale: String!\n $churnType: String!\n ) {\n offerings(\n filters: {\n or: [\n { stripeProductId: { eq: $stripeProductId } }\n { apiIdentifier: { eq: $offeringApiIdentifier } }\n ]\n }\n pagination: { limit: 200 }\n ) {\n defaultPurchase {\n purchaseDetails {\n productName\n webIcon\n localizations(filters: { locale: { eq: $locale } }) {\n productName\n webIcon\n }\n }\n }\n commonContent {\n supportUrl\n }\n churnInterventions(\n filters: { interval: { eq: $interval }, churnType: { eq: $churnType } }\n pagination: { limit: 200 }\n ) {\n localizations(filters: { locale: { eq: $locale } }) {\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n churnInterventionId\n churnType\n redemptionLimit\n stripeCouponId\n interval\n discountAmount\n ctaMessage\n modalHeading\n modalMessage\n productPageUrl\n termsHeading\n termsDetails\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
4
libs/shared/cms/src/__generated__/graphql.ts
generated
4
libs/shared/cms/src/__generated__/graphql.ts
generated
File diff suppressed because one or more lines are too long
@@ -40,9 +40,11 @@ export const ChurnInterventionByProductIdOfferingsResultFactory = (
|
||||
): ChurnInterventionByProductIdOfferingResult => ({
|
||||
defaultPurchase: {
|
||||
purchaseDetails: {
|
||||
productName: faker.string.sample(),
|
||||
webIcon: faker.image.urlLoremFlickr(),
|
||||
localizations: [
|
||||
{
|
||||
productName: faker.string.sample(),
|
||||
webIcon: faker.image.urlLoremFlickr(),
|
||||
},
|
||||
],
|
||||
@@ -77,6 +79,7 @@ export const ChurnInterventionByProductIdRawResultFactory = (
|
||||
export const ChurnInterventionByProductIdResultFactory = (
|
||||
override?: Partial<ChurnInterventionByProductIdResult>
|
||||
): ChurnInterventionByProductIdResult => ({
|
||||
productName: faker.string.sample(),
|
||||
webIcon: faker.image.urlLoremFlickr(),
|
||||
churnInterventionId: faker.string.uuid(),
|
||||
churnType: faker.helpers.enumValue(Enum_Churnintervention_Churntype),
|
||||
|
||||
@@ -23,8 +23,10 @@ export const churnInterventionByProductIdQuery = graphql(`
|
||||
) {
|
||||
defaultPurchase {
|
||||
purchaseDetails {
|
||||
productName
|
||||
webIcon
|
||||
localizations(filters: { locale: { eq: $locale } }) {
|
||||
productName
|
||||
webIcon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ export interface ChurnInterventionByProductIdChurnInterventionsResult {
|
||||
export interface ChurnInterventionByProductIdOfferingResult {
|
||||
defaultPurchase: {
|
||||
purchaseDetails: {
|
||||
productName: string;
|
||||
webIcon: string;
|
||||
localizations: { webIcon: string }[];
|
||||
localizations: { productName: string; webIcon: string }[];
|
||||
};
|
||||
};
|
||||
commonContent: {
|
||||
@@ -42,6 +43,7 @@ export interface ChurnInterventionByProductIdRawResult {
|
||||
}
|
||||
|
||||
export interface ChurnInterventionByProductIdResult {
|
||||
productName: string;
|
||||
webIcon: string;
|
||||
churnInterventionId: string;
|
||||
churnType: Enum_Churnintervention_Churntype;
|
||||
|
||||
@@ -36,6 +36,7 @@ describe('ChurnInterventionByProductIdResultUtil', () => {
|
||||
it('should transform churn intervention by offering', () => {
|
||||
const transformed = util.getTransformedChurnInterventionByProductId()[0];
|
||||
expect(transformed).toBeDefined();
|
||||
expect(transformed?.productName).toBeDefined();
|
||||
expect(transformed?.webIcon).toBeDefined();
|
||||
expect(transformed?.supportUrl).toBeDefined();
|
||||
expect(transformed?.churnInterventionId).toBeDefined();
|
||||
|
||||
@@ -30,6 +30,10 @@ export class ChurnInterventionByProductIdResultUtil {
|
||||
defaultPurchase.purchaseDetails.localizations.length > 0
|
||||
? defaultPurchase.purchaseDetails.localizations[0].webIcon
|
||||
: defaultPurchase.purchaseDetails.webIcon,
|
||||
productName:
|
||||
defaultPurchase.purchaseDetails.localizations.length > 0
|
||||
? defaultPurchase.purchaseDetails.localizations[0].productName
|
||||
: defaultPurchase.purchaseDetails.productName,
|
||||
supportUrl: commonContent.supportUrl,
|
||||
ctaMessage:
|
||||
churnIntervention.localizations.at(0)?.ctaMessage ??
|
||||
|
||||
@@ -15,6 +15,7 @@ interface LinkExternalProps {
|
||||
rel?: 'noopener noreferrer' | 'author';
|
||||
tabIndex?: number;
|
||||
onClick?: () => void;
|
||||
'aria-label'?: string;
|
||||
}
|
||||
|
||||
export const LinkExternal = ({
|
||||
@@ -26,6 +27,7 @@ export const LinkExternal = ({
|
||||
rel = 'noopener noreferrer',
|
||||
tabIndex,
|
||||
onClick,
|
||||
'aria-label': ariaLabel,
|
||||
}: LinkExternalProps) => (
|
||||
<a
|
||||
data-testid={testid}
|
||||
@@ -37,6 +39,7 @@ export const LinkExternal = ({
|
||||
rel,
|
||||
tabIndex,
|
||||
onClick,
|
||||
'aria-label': ariaLabel,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user