Skip to content

Commit 4ad6d2e

Browse files
author
Raphael Kabo
committed
Merge remote-tracking branch 'origin/main' into rk/oauth-migration-idapi-aapi
2 parents 519512f + 379d7d9 commit 4ad6d2e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1359
-719
lines changed

.github/workflows/cypress-e2e.yml

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: manage-frontend cypress (E2E)
22
on:
3+
push:
4+
branches:
5+
- main
36
pull_request:
47
branches:
58
- main

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ DEV only Dependencies
2020

2121
## Dependencies
2222

23-
- [`identity-frontend`](https://github.com/guardian/identity-frontend)
23+
- [`gateway`](https://github.com/guardian/gateway)
2424
- [IDAPI](https://github.com/guardian/identity)
2525
- [`members-data-api`](https://github.com/guardian/members-data-api)
2626
- [`holiday-stop-api` ](https://github.com/guardian/support-service-lambdas/tree/master/handlers/holiday-stop-api)
@@ -31,7 +31,11 @@ DEV only Dependencies
3131

3232
## Basic DEV environment
3333

34-
NOTE: nginx proxies CODE instances of [`identity-frontend`](https://github.com/guardian/identity-frontend) for sign-in and [`members-data-api`](https://github.com/guardian/members-data-api) **if they're not running locally**. See sections further down if you need to actually develop either of those services in parallel with `manage-frontend`.
34+
NOTE: nginx proxies CODE instances of [`gateway`] for sign-in and [`members-data-api`](https://github.com/guardian/members-data-api) **if they're not running locally**. See sections further down if you need to actually develop either of those services in parallel with `manage-frontend`.
35+
36+
**Note:** When using the local proxy, the sign-in flow for social logins (for example Google) doesn't quite work as the provider has no knowledge of the proxy and redirects back to CODE which doesn't know how to handle the request. The solution is to either use a username/password to sign in or if you need social login run [`gateway`] locally.
37+
38+
[`gateway`]: https://github.com/guardian/gateway
3539

3640
#### One-off setup
3741

client/HelpCentrePage.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ if (typeof window !== 'undefined' && window.guardian && window.guardian.dsn) {
1313
release: WEBPACK_BUILD || 'local',
1414
environment: window.guardian.domain,
1515
});
16+
17+
Sentry.setTag('gu:referrer', document.referrer || 'none');
1618
}
1719

1820
const element = document.getElementById('app');

client/__tests__/components/payment/cardFlow.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const executePaymentUpdate = jest.fn((_: NewPaymentMethodDetail) =>
1717
Promise.resolve(null),
1818
);
1919

20-
const stripePaymentMethod: PaymentMethod | unknown = {
20+
const stripePaymentMethod: Partial<PaymentMethod> = {
2121
id: 'stripePaymentMethodId',
2222
created: 0,
2323
card: {

client/components/helpCentre/HelpCentrePage.tsx

+1-7
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,7 @@ const HelpCentreRouter = () => {
7777
]
7878
*/
7979

80-
const knownIssues: KnownIssueObj[] = [
81-
{
82-
date: '4th Dec 2023 12:45',
83-
message:
84-
'Due to a technical issue, Customer Service phonelines in the USA & Canada are currently not available. Live chat and email are unaffected.',
85-
},
86-
];
80+
const knownIssues: KnownIssueObj[] = [];
8781

8882
return (
8983
<Main signInStatus={signInStatus} isHelpCentrePage>

client/components/helpCentre/liveChat/LiveChat.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ let areAgentsAvailable = false;
1616

1717
const initESW = (
1818
gslbBaseUrl: string | null,
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Salesforce provides this object
1920
liveChatAPI: any,
2021
targetElement: HTMLElement,
2122
identityID: string,

client/components/mma/MMAPage.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ import { Main } from '../shared/Main';
3535
import { DeliveryAddressUpdate } from './delivery/address/DeliveryAddressForm';
3636
import { Maintenance } from './maintenance/Maintenance';
3737
import { MMAPageSkeleton } from './MMAPageSkeleton';
38+
import { SignInError } from './signInError/SignInError';
3839

40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Ophan events are diverse (and unguessable?)
3941
const record = (event: any) => {
4042
if (window.guardian?.ophan?.record) {
4143
window.guardian.ophan.record(event);
@@ -781,7 +783,13 @@ const MMARouter = () => {
781783
<CreateReminder reminderType={'RECURRING'} />
782784
}
783785
/>
786+
{/*Does not require sign in*/}
784787
<Route path="/maintenance" element={<Maintenance />} />
788+
{/*Does not require sign in*/}
789+
<Route
790+
path="/sign-in-error"
791+
element={<SignInError />}
792+
/>
785793
<Route path="*" element={<Navigate to="/" />} />
786794
</Routes>
787795
</ErrorBoundary>

client/components/mma/accountoverview/manageProducts/ManageProductV2.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const InnerContent = ({
9393

9494
const hasCancellationPending = productDetail.subscription.cancelledAt;
9595

96+
const isSelfServeCancellationAllowed = productDetail.selfServiceCancellation.isAllowed;
97+
9698
const cancelledCopy =
9799
specificProductType.cancelledCopy || groupedProductType.cancelledCopy;
98100

@@ -250,7 +252,7 @@ const InnerContent = ({
250252
margin-left: ${space[5]}px;
251253
`}
252254
>
253-
{!hasCancellationPending && (
255+
{!hasCancellationPending && isSelfServeCancellationAllowed && (
254256
<Button
255257
priority="subdued"
256258
onClick={() => {

client/components/mma/cancel/stages/ExecuteCancellation.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ const getCancellationSummaryWithReturnButton = (body: ReactNode) => () =>
110110

111111
const getCaseUpdatingCancellationSummary =
112112
(
113-
caseId: string | '',
113+
caseId: string,
114114
productType: ProductTypeWithCancellationFlow,
115115
cancelledProductDetail: ProductDetail,
116116
) =>

client/components/mma/identity/idapi/avatar.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface AvatarNotFoundError {
2020
type AvatarError = AvatarValidationError | AvatarNotFoundError;
2121

2222
const isAvatarAPIErrorResponse = (
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we rely on Avatar returning something; we check to see if it is an error response
2324
response: any,
2425
): response is AvatarAPIErrorResponse => {
2526
if (response.message && response.errors) {

client/components/mma/identity/idapi/user.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ interface UserAPIErrorResponse {
7979
const getOrEmpty = (user: IdapiUserDetails) => (path: string) =>
8080
get(user, path, '');
8181

82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we check input to see if it's a UserAPIErrorResponse object
8283
const isErrorResponse = (error: any): error is UserAPIErrorResponse => {
8384
return error.status && error.status === 'error';
8485
};

client/components/mma/identity/publicProfile/AvatarSection.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ const imgCss = css({
3232
width: '60px',
3333
});
3434

35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the argument object is an error object?
3536
const isEmptyAvatarError = (e: any): boolean => {
3637
return e.type && e.type === ErrorTypes.NOT_FOUND;
3738
};
3839

40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the argument object is an error object?
3941
const errorHandler = (e: any) => {
4042
if (isEmptyAvatarError(e)) {
4143
return;
@@ -136,6 +138,7 @@ export const AvatarSection: FC<AvatarSectionProps> = (props) => {
136138
</div>
137139
);
138140

141+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the argument object might lack a message attribute
139142
const getErrorMessage = (error: any) => {
140143
let message;
141144
if (error.type && error.type === ErrorTypes.VALIDATION) {

client/components/mma/identity/publicProfile/ProfileFormSection.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { textSmall } from '../sharedStyles';
1212
interface ProfileFormSectionProps {
1313
user: User;
1414
saveUser: (values: User) => Promise<User>;
15+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the onError function's argument is an error object?
1516
onError: (error: any) => void;
1617
onSuccess: (user: User) => void;
1718
}

client/components/mma/identity/publicProfile/PublicProfile.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const PublicProfile = (_: { path?: string }) => {
2424

2525
const errorRef = createRef<GenericErrorMessageRef>();
2626

27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the argument object is an error object?
2728
const handleGeneralError = (e: any) => {
2829
setError(true);
2930
Sentry.captureException(e);

client/components/mma/identity/settings/Settings.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const Settings = (_: { path?: string }) => {
3232
}
3333
}, [error]);
3434

35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the argument object is an error object?
3536
const handleGeneralError = (e: any) => {
3637
setError(true);
3738
Sentry.captureException(e);

client/components/mma/identity/settings/SettingsFormSection.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { aCss, textSmall } from '../sharedStyles';
2828
interface SettingsFormProps {
2929
user: User;
3030
saveUser: (values: User) => Promise<User>;
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the onError function's argument is an error object?
3132
onError: (error: any) => void;
3233
onSuccess: (input: User, response: User) => void;
3334
onDone: () => void;

client/components/mma/identity/useAsyncSource.ts

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ enum ActionType {
88

99
interface Action {
1010
type: ActionType;
11+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the payload attribute can contain a range of differently typed things?
1112
payload?: any;
1213
}
1314

@@ -19,12 +20,15 @@ enum FetchStatus {
1920
}
2021

2122
interface ApiFetchState {
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the data attribute can contain a range of differently typed things?
2224
data: any;
2325
error: string | null;
2426
status: FetchStatus;
2527
}
2628

2729
const ActionFetch = (): Action => ({ type: ActionType.FETCH });
30+
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the ActionSuccess function's argument can contain a range of differently typed things?
2832
const ActionSuccess = (payload: any): Action => ({
2933
type: ActionType.SUCCESS,
3034
payload,
@@ -68,10 +72,14 @@ const reducer = (state: ApiFetchState, action: Action): ApiFetchState => {
6872
};
6973

7074
export const useAsyncSource = (
75+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the array can contain a range of differently typed things? And the promise is un-type-able?
7176
getter: (...args: any[]) => Promise<any>,
77+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the errorHandler function's argument is an error object?
7278
errorHandler?: (e: any) => void,
79+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the array can contain a range of differently typed things?
7380
): [ApiFetchState, (...args: any[]) => Promise<ApiFetchState>] => {
7481
const [state, dispatch] = useReducer(reducer, initialState);
82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're only assuming the doFetch function's argument can contain a range of differently typed things?
7583
const doFetch = async (...args: any[]) => {
7684
dispatch(ActionFetch());
7785
try {

client/components/mma/identity/useConsentOptions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum ActionType {
1515

1616
interface Action {
1717
type: ActionType;
18+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the payload attribute can contain a range of differently typed things?
1819
payload?: any;
1920
}
2021

client/components/mma/paymentUpdate/FieldWrapper.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export class FieldWrapper extends React.Component<
126126
}
127127

128128
private validateField =
129+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the argument object is an event object?
129130
(otherOnChange?: (event: any) => void) =>
130131
(field: { error: StripeError }) => {
131132
if (otherOnChange) {

client/components/mma/paymentUpdate/NewPaymentMethodDetail.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface NewPaymentMethodDetail {
88
readonly name: PaymentUpdateTypeName;
99
readonly apiUrlPart: PaymentUpdateTypeApiUrlPart;
1010
readonly detailToPayloadObject: () => object;
11+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the matchesResponse argument can be anything?
1112
readonly matchesResponse: (response: any) => boolean;
1213
readonly subHasExpectedPaymentType: (
1314
subscription?: Subscription,

client/components/mma/shared/AsyncLoader.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export class AsyncLoader<
121121
trackEvent({
122122
eventCategory: 'asyncLoader',
123123
eventAction: 'error',
124+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- Error.toString will output a string
124125
eventLabel: error ? error.toString() : undefined,
125126
});
126127
Sentry.captureException(error);

client/components/mma/shared/asyncComponents/DefaultApiResponseHandler.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export const TextResponseHandler: ResponseProcessor = (
1414

1515
export function handleResponses(
1616
response: Response | Response[],
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the transformResponse attribute's output can be of multiple types
1718
transformResponse: (response: Response) => any,
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we don't know the final output of the promise?
1820
): Promise<any> {
1921
if (hasBadResponse(response)) {
2022
throw new Error('Invalid API response');
@@ -37,7 +39,8 @@ function hasBadResponse(responses: Response | Response[]) {
3739
return !responses.ok;
3840
}
3941

40-
function handleSingleResponse(response: Response, transformResponse: any) {
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're assuming the transformResponse argument's output can be of multiple types
43+
function handleSingleResponse(response: Response, transformResponse: (r: Response) => any) {
4144
const locationHeader = response.headers.get('Location');
4245
if (response.status === 401 && locationHeader && window !== undefined) {
4346
window.location.replace(locationHeader);

client/components/mma/shared/asyncComponents/LoadingComponent.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import {
55
import type { ResponseProcessor } from './ResponseProcessor';
66

77
export type LoadingProps = {
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- assume we don't know the final output of the promise
89
asyncFetch: () => Promise<any>;
910
responseProcessor: ResponseProcessor;
1011
LoadingView: () => React.ReactElement;
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- assume we don't know the shape of the data argument
1113
LoadedView: (data: any) => React.ReactElement;
1214
ErrorView: () => React.ReactElement;
1315
};
@@ -19,6 +21,7 @@ export function LoadingComponent({
1921
LoadedView,
2022
ErrorView,
2123
}: LoadingProps) {
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- assume we don't know the final output of useAsyncLoader's returned promise
2225
const { data, loadingState } = useAsyncLoader<any>(
2326
asyncFetch,
2427
responseProcessor,
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export interface ResponseProcessor {
2+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- assume we don't know the final output of the promise
23
(resp: Response | Response[]): Promise<any>;
34
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Meta, StoryFn } from '@storybook/react';
2+
import { ReactRouterDecorator } from '@/.storybook/ReactRouterDecorator';
3+
import { SignInError } from './SignInError';
4+
5+
export default {
6+
title: 'Pages/SignInError',
7+
component: SignInError,
8+
decorators: [ReactRouterDecorator],
9+
parameters: {
10+
layout: 'fullscreen',
11+
},
12+
} as Meta<typeof SignInError>;
13+
14+
export const Default: StoryFn<typeof SignInError> = () => <SignInError />;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { css } from '@emotion/react';
2+
import {
3+
breakpoints,
4+
from,
5+
headline,
6+
neutral,
7+
space,
8+
textSans,
9+
} from '@guardian/source-foundations';
10+
import { LinkButton } from '@guardian/source-react-components';
11+
import { conf } from '@/server/config';
12+
13+
const containerStyle = css`
14+
max-width: ${breakpoints.wide}px;
15+
margin: 0 auto;
16+
padding: ${space[12]}px 0;
17+
border-left: 1px solid ${neutral[86]};
18+
border-right: 1px solid ${neutral[86]};
19+
height: 100%;
20+
`;
21+
22+
const wrapperStyle = css`
23+
margin: 0 10px;
24+
max-width: ${breakpoints.mobileLandscape}px;
25+
${from.tablet} {
26+
margin: 0 20px;
27+
}
28+
`;
29+
30+
const headingStyle = css`
31+
${headline.medium({ fontWeight: 'bold' })};
32+
margin: 0;
33+
padding-bottom: ${space[4]}px;
34+
border-bottom: 1px solid ${neutral['86']};
35+
`;
36+
37+
const grafStyle = css`
38+
${textSans.medium()};
39+
margin-top: ${space[4]}px;
40+
margin-bottom: ${space[4]}px;
41+
`;
42+
43+
export const SignInError = () => {
44+
const domain =
45+
typeof window !== 'undefined' && window.guardian
46+
? window.guardian.domain
47+
: conf.DOMAIN;
48+
const signOutUrl = `https://profile.${domain}/signout?returnUrl=${encodeURIComponent(
49+
`https://manage.${domain}/`,
50+
)}`;
51+
52+
return (
53+
<div css={containerStyle}>
54+
<section css={wrapperStyle}>
55+
<h1 css={headingStyle}>Sign in error</h1>
56+
<p css={grafStyle}>
57+
There's been a problem signing you in. Please sign in again
58+
to continue managing your account.
59+
</p>
60+
61+
<LinkButton href={signOutUrl}>Continue</LinkButton>
62+
</section>
63+
</div>
64+
);
65+
};

client/components/shared/SectionContent.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const divCss = (hasNav: boolean | undefined) => css`
8484

8585
export const SelectedTopicObjectContext = createContext<
8686
Dispatch<SetStateAction<string | undefined>>
87-
>(undefined!); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- // TODO: refactor this var to remove need for disabling eslint rule
87+
>(undefined!);
8888

8989
export const SectionContent = (props: SectionContentProps) => {
9090
const [selectedTopicId, setSelectedTopicId] = useState<string | undefined>(

0 commit comments

Comments
 (0)