|
@@ -1,6 +1,5 @@
|
|
|
-import {Fragment, useState} from 'react';
|
|
|
+import {Fragment, useEffect, useState} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
-import isEqual from 'lodash/isEqual';
|
|
|
|
|
|
import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
|
|
|
import {ModalRenderProps} from 'app/actionCreators/modal';
|
|
@@ -8,18 +7,32 @@ import {Client} from 'app/api';
|
|
|
import Alert from 'app/components/alert';
|
|
|
import Button from 'app/components/button';
|
|
|
import ButtonBar from 'app/components/buttonBar';
|
|
|
-import List from 'app/components/list';
|
|
|
-import {IconInfo, IconWarning} from 'app/icons';
|
|
|
-import {t} from 'app/locale';
|
|
|
-import space from 'app/styles/space';
|
|
|
+import {
|
|
|
+ appStoreConnectAlertMessage,
|
|
|
+ getAppConnectStoreUpdateAlertMessage,
|
|
|
+} from 'app/components/globalAppStoreConnectUpdateAlert/utils';
|
|
|
+import LoadingIndicator from 'app/components/loadingIndicator';
|
|
|
+import {AppStoreConnectContextProps} from 'app/components/projects/appStoreConnectContext';
|
|
|
+import {IconWarning} from 'app/icons';
|
|
|
+import {t, tct} from 'app/locale';
|
|
|
+import space, {ValidSize} from 'app/styles/space';
|
|
|
import {Organization, Project} from 'app/types';
|
|
|
-import {AppStoreConnectValidationData} from 'app/types/debugFiles';
|
|
|
import withApi from 'app/utils/withApi';
|
|
|
|
|
|
-import Accordion from './accordion';
|
|
|
-import AppStoreCredentials from './appStoreCredentials';
|
|
|
-import ItunesCredentials from './itunesCredentials';
|
|
|
-import {AppStoreCredentialsData, ItunesCredentialsData} from './types';
|
|
|
+import StepFifth from './stepFifth';
|
|
|
+import StepFour from './stepFour';
|
|
|
+import StepOne from './stepOne';
|
|
|
+import StepThree from './stepThree';
|
|
|
+import StepTwo from './stepTwo';
|
|
|
+import {
|
|
|
+ AppleStoreOrg,
|
|
|
+ AppStoreApp,
|
|
|
+ StepFifthData,
|
|
|
+ StepFourData,
|
|
|
+ StepOneData,
|
|
|
+ StepThreeData,
|
|
|
+ StepTwoData,
|
|
|
+} from './types';
|
|
|
|
|
|
type IntialData = {
|
|
|
appId: string;
|
|
@@ -38,17 +51,26 @@ type IntialData = {
|
|
|
type: string;
|
|
|
};
|
|
|
|
|
|
-type Props = Pick<ModalRenderProps, 'Body' | 'Footer' | 'closeModal'> & {
|
|
|
+type Props = Pick<ModalRenderProps, 'Header' | 'Body' | 'Footer' | 'closeModal'> & {
|
|
|
api: Client;
|
|
|
orgSlug: Organization['slug'];
|
|
|
projectSlug: Project['slug'];
|
|
|
onSubmit: (data: Record<string, any>) => void;
|
|
|
revalidateItunesSession: boolean;
|
|
|
- appStoreConnectValidationData?: AppStoreConnectValidationData;
|
|
|
+ appStoreConnectContext?: AppStoreConnectContextProps;
|
|
|
initialData?: IntialData;
|
|
|
};
|
|
|
|
|
|
+const steps = [
|
|
|
+ t('App Store Connect credentials'),
|
|
|
+ t('Choose an application'),
|
|
|
+ t('Enter iTunes credentials'),
|
|
|
+ t('Enter authentication code'),
|
|
|
+ t('Choose an organization'),
|
|
|
+];
|
|
|
+
|
|
|
function AppStoreConnect({
|
|
|
+ Header,
|
|
|
Body,
|
|
|
Footer,
|
|
|
closeModal,
|
|
@@ -58,258 +80,502 @@ function AppStoreConnect({
|
|
|
projectSlug,
|
|
|
onSubmit,
|
|
|
revalidateItunesSession,
|
|
|
- appStoreConnectValidationData,
|
|
|
+ appStoreConnectContext,
|
|
|
}: Props) {
|
|
|
- const isUpdating = !!initialData;
|
|
|
- const appStoreCredentialsInvalid =
|
|
|
- appStoreConnectValidationData?.appstoreCredentialsValid === false;
|
|
|
- const itunesSessionInvalid =
|
|
|
- appStoreConnectValidationData?.itunesSessionValid === false;
|
|
|
+ const shouldRevalidateItunesSession =
|
|
|
+ revalidateItunesSession &&
|
|
|
+ (appStoreConnectContext?.itunesSessionValid === false ||
|
|
|
+ appStoreConnectContext?.appstoreCredentialsValid === false);
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
- const [isEditingAppStoreCredentials, setIsEditingAppStoreCredentials] = useState(
|
|
|
- appStoreCredentialsInvalid || !isUpdating
|
|
|
- );
|
|
|
- const [isEditingItunesCredentials, setIsEditingItunesCredentials] = useState(
|
|
|
- itunesSessionInvalid || !isUpdating
|
|
|
- );
|
|
|
+ const [activeStep, setActiveStep] = useState(shouldRevalidateItunesSession ? 2 : 0);
|
|
|
+ const [appStoreApps, setAppStoreApps] = useState<AppStoreApp[]>([]);
|
|
|
+ const [appleStoreOrgs, setAppleStoreOrgs] = useState<AppleStoreOrg[]>([]);
|
|
|
+ const [useSms, setUseSms] = useState(false);
|
|
|
+ const [sessionContext, setSessionContext] = useState('');
|
|
|
|
|
|
- const appStoreCredentialsInitialData = {
|
|
|
+ const [stepOneData, setStepOneData] = useState<StepOneData>({
|
|
|
issuer: initialData?.appconnectIssuer,
|
|
|
keyId: initialData?.appconnectKey,
|
|
|
privateKey: initialData?.appconnectPrivateKey,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [stepTwoData, setStepTwoData] = useState<StepTwoData>({
|
|
|
app:
|
|
|
- initialData?.appName && initialData?.appId
|
|
|
- ? {
|
|
|
- appId: initialData.appId,
|
|
|
- name: initialData.appName,
|
|
|
- }
|
|
|
+ initialData?.appId && initialData?.appName
|
|
|
+ ? {appId: initialData.appId, name: initialData.appName}
|
|
|
: undefined,
|
|
|
- };
|
|
|
+ });
|
|
|
|
|
|
- const iTunesCredentialsInitialData = {
|
|
|
+ const [stepThreeData, setStepThreeData] = useState<StepThreeData>({
|
|
|
username: initialData?.itunesUser,
|
|
|
password: initialData?.itunesPassword,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [stepFourData, setStepFourData] = useState<StepFourData>({
|
|
|
authenticationCode: undefined,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [stepFifthData, setStepFifthData] = useState<StepFifthData>({
|
|
|
org:
|
|
|
- initialData?.orgId && initialData?.orgName
|
|
|
- ? {
|
|
|
- organizationId: initialData.orgId,
|
|
|
- name: initialData.appName,
|
|
|
- }
|
|
|
+ initialData?.orgId && initialData?.name
|
|
|
+ ? {organizationId: initialData.orgId, name: initialData.name}
|
|
|
: undefined,
|
|
|
- useSms: undefined,
|
|
|
- sessionContext: undefined,
|
|
|
- };
|
|
|
-
|
|
|
- const [
|
|
|
- appStoreCredentialsData,
|
|
|
- setAppStoreCredentialsData,
|
|
|
- ] = useState<AppStoreCredentialsData>(appStoreCredentialsInitialData);
|
|
|
-
|
|
|
- const [
|
|
|
- iTunesCredentialsData,
|
|
|
- setItunesCredentialsData,
|
|
|
- ] = useState<ItunesCredentialsData>(iTunesCredentialsInitialData);
|
|
|
-
|
|
|
- function isDataInvalid(data: Record<string, any>) {
|
|
|
- return Object.keys(data).some(key => {
|
|
|
- const value = data[key];
|
|
|
-
|
|
|
- if (typeof value === 'string') {
|
|
|
- return !value.trim();
|
|
|
- }
|
|
|
+ });
|
|
|
|
|
|
- return typeof value === 'undefined';
|
|
|
- });
|
|
|
- }
|
|
|
+ useEffect(() => {
|
|
|
+ if (shouldRevalidateItunesSession) {
|
|
|
+ handleStartItunesAuthentication();
|
|
|
+ }
|
|
|
+ }, [shouldRevalidateItunesSession]);
|
|
|
|
|
|
- function isAppStoreCredentialsDataInvalid() {
|
|
|
- return isDataInvalid(appStoreCredentialsData);
|
|
|
- }
|
|
|
+ async function checkAppStoreConnectCredentials() {
|
|
|
+ setIsLoading(true);
|
|
|
+ try {
|
|
|
+ const response = await api.requestPromise(
|
|
|
+ `/projects/${orgSlug}/${projectSlug}/appstoreconnect/apps/`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ appconnectIssuer: stepOneData.issuer,
|
|
|
+ appconnectKey: stepOneData.keyId,
|
|
|
+ appconnectPrivateKey: stepOneData.privateKey,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- function isItunesCredentialsDataInvalid() {
|
|
|
- return isDataInvalid(iTunesCredentialsData);
|
|
|
+ setAppStoreApps(response.apps);
|
|
|
+ setStepTwoData({app: response.apps[0]});
|
|
|
+ setIsLoading(false);
|
|
|
+ goNext();
|
|
|
+ } catch (error) {
|
|
|
+ setIsLoading(false);
|
|
|
+ addErrorMessage(
|
|
|
+ t(
|
|
|
+ 'We could not establish a connection with App Store Connect. Please check the entered App Store Connect credentials.'
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- function isFormInvalid() {
|
|
|
- if (!!initialData) {
|
|
|
- const isAppStoreCredentialsDataTheSame = isEqual(
|
|
|
- appStoreCredentialsData,
|
|
|
- appStoreCredentialsInitialData
|
|
|
+ async function startTwoFactorAuthentication() {
|
|
|
+ setIsLoading(true);
|
|
|
+ try {
|
|
|
+ const response = await api.requestPromise(
|
|
|
+ `/projects/${orgSlug}/${projectSlug}/appstoreconnect/2fa/`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ code: stepFourData.authenticationCode,
|
|
|
+ useSms,
|
|
|
+ sessionContext,
|
|
|
+ },
|
|
|
+ }
|
|
|
);
|
|
|
-
|
|
|
- const isItunesCredentialsDataTheSame = isEqual(
|
|
|
- iTunesCredentialsData,
|
|
|
- iTunesCredentialsInitialData
|
|
|
+ setIsLoading(false);
|
|
|
+ const {organizations, sessionContext: newSessionContext} = response;
|
|
|
+ setStepFifthData({org: organizations[0]});
|
|
|
+ setAppleStoreOrgs(organizations);
|
|
|
+ setSessionContext(newSessionContext);
|
|
|
+ goNext();
|
|
|
+ } catch (error) {
|
|
|
+ setIsLoading(false);
|
|
|
+ addErrorMessage(
|
|
|
+ t('The two factor authentication failed. Please check the entered code.')
|
|
|
);
|
|
|
-
|
|
|
- if (!isAppStoreCredentialsDataTheSame && !isItunesCredentialsDataTheSame) {
|
|
|
- return isAppStoreCredentialsDataInvalid() && isItunesCredentialsDataInvalid();
|
|
|
- }
|
|
|
-
|
|
|
- if (!isAppStoreCredentialsDataTheSame) {
|
|
|
- return isAppStoreCredentialsDataInvalid();
|
|
|
- }
|
|
|
-
|
|
|
- if (!isItunesCredentialsDataTheSame) {
|
|
|
- return isItunesCredentialsDataInvalid();
|
|
|
- }
|
|
|
-
|
|
|
- return isAppStoreCredentialsDataTheSame && isItunesCredentialsDataTheSame;
|
|
|
}
|
|
|
-
|
|
|
- return isAppStoreCredentialsDataInvalid() && isItunesCredentialsDataInvalid();
|
|
|
}
|
|
|
|
|
|
- async function handleSave() {
|
|
|
+ async function persistData() {
|
|
|
+ if (!stepTwoData.app || !stepFifthData.org || !stepThreeData.username) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setIsLoading(true);
|
|
|
+
|
|
|
let endpoint = `/projects/${orgSlug}/${projectSlug}/appstoreconnect/`;
|
|
|
- let successMessage = t('App Store Connect repository was successfully added');
|
|
|
+ let successMessage = t('App Store Connect repository was successfully added.');
|
|
|
let errorMessage = t(
|
|
|
- 'An error occured while adding the App Store Connect repository'
|
|
|
+ 'An error occured while adding the App Store Connect repository.'
|
|
|
);
|
|
|
|
|
|
if (!!initialData) {
|
|
|
endpoint = `${endpoint}${initialData.id}/`;
|
|
|
- successMessage = t('App Store Connect repository was successfully updated');
|
|
|
+ successMessage = t('App Store Connect repository was successfully updated.');
|
|
|
errorMessage = t(
|
|
|
- 'An error occured while updating the App Store Connect repository'
|
|
|
+ 'An error occured while updating the App Store Connect repository.'
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- setIsLoading(true);
|
|
|
try {
|
|
|
const response = await api.requestPromise(endpoint, {
|
|
|
method: 'POST',
|
|
|
data: {
|
|
|
- appconnectIssuer: appStoreCredentialsData.issuer,
|
|
|
- appconnectKey: appStoreCredentialsData.keyId,
|
|
|
- appconnectPrivateKey: appStoreCredentialsData.privateKey,
|
|
|
- appName: appStoreCredentialsData.app?.name,
|
|
|
- appId: appStoreCredentialsData.app?.appId,
|
|
|
- itunesUser: iTunesCredentialsData.username,
|
|
|
- itunesPassword: iTunesCredentialsData.password,
|
|
|
- orgId: iTunesCredentialsData.org?.organizationId,
|
|
|
- orgName: iTunesCredentialsData.org?.name,
|
|
|
- sessionContext: iTunesCredentialsData.sessionContext,
|
|
|
+ itunesUser: stepThreeData.username,
|
|
|
+ itunesPassword: stepThreeData.password,
|
|
|
+ appconnectIssuer: stepOneData.issuer,
|
|
|
+ appconnectKey: stepOneData.keyId,
|
|
|
+ appconnectPrivateKey: stepOneData.privateKey,
|
|
|
+ appName: stepTwoData.app.name,
|
|
|
+ appId: stepTwoData.app.appId,
|
|
|
+ orgId: stepFifthData.org.organizationId,
|
|
|
+ orgName: stepFifthData.org.name,
|
|
|
+ sessionContext,
|
|
|
},
|
|
|
});
|
|
|
addSuccessMessage(successMessage);
|
|
|
- setIsLoading(false);
|
|
|
onSubmit(response);
|
|
|
closeModal();
|
|
|
- } catch {
|
|
|
+ } catch (error) {
|
|
|
setIsLoading(false);
|
|
|
addErrorMessage(errorMessage);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function handleEditAppStoreCredentials(isEditing: boolean) {
|
|
|
+ function isFormInvalid() {
|
|
|
+ switch (activeStep) {
|
|
|
+ case 0:
|
|
|
+ return Object.keys(stepOneData).some(key => !stepOneData[key]);
|
|
|
+ case 1:
|
|
|
+ return Object.keys(stepTwoData).some(key => !stepTwoData[key]);
|
|
|
+ case 2: {
|
|
|
+ return Object.keys(stepThreeData).some(key => !stepThreeData[key]);
|
|
|
+ }
|
|
|
+ case 3: {
|
|
|
+ return Object.keys(stepFourData).some(key => !stepFourData[key]);
|
|
|
+ }
|
|
|
+ case 4: {
|
|
|
+ return Object.keys(stepFifthData).some(key => !stepFifthData[key]);
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function goNext() {
|
|
|
+ setActiveStep(activeStep + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleStartItunesAuthentication(shouldGoNext = true) {
|
|
|
+ if (shouldGoNext) {
|
|
|
+ setIsLoading(true);
|
|
|
+ }
|
|
|
+ if (useSms) {
|
|
|
+ setUseSms(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await api.requestPromise(
|
|
|
+ `/projects/${orgSlug}/${projectSlug}/appstoreconnect/start/`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ itunesUser: stepThreeData.username,
|
|
|
+ itunesPassword: stepThreeData.password,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ setSessionContext(response.sessionContext);
|
|
|
+
|
|
|
+ if (shouldGoNext) {
|
|
|
+ setIsLoading(false);
|
|
|
+ goNext();
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ if (shouldGoNext) {
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ addErrorMessage(
|
|
|
+ t('The iTunes authentication failed. Please check the entered credentials.')
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleStartSmsAuthentication() {
|
|
|
+ if (!useSms) {
|
|
|
+ setUseSms(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await api.requestPromise(
|
|
|
+ `/projects/${orgSlug}/${projectSlug}/appstoreconnect/requestSms/`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ data: {sessionContext},
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ setSessionContext(response.sessionContext);
|
|
|
+ } catch {
|
|
|
+ addErrorMessage(t('An error occured while sending the SMS. Please try again'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleGoBack() {
|
|
|
+ const newActiveStep = activeStep - 1;
|
|
|
+
|
|
|
+ switch (newActiveStep) {
|
|
|
+ case 3:
|
|
|
+ handleStartItunesAuthentication(false);
|
|
|
+ setStepFourData({authenticationCode: undefined});
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ setActiveStep(newActiveStep);
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleGoNext() {
|
|
|
+ switch (activeStep) {
|
|
|
+ case 0:
|
|
|
+ checkAppStoreConnectCredentials();
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ goNext();
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ handleStartItunesAuthentication();
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ startTwoFactorAuthentication();
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ persistData();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderCurrentStep() {
|
|
|
+ switch (activeStep) {
|
|
|
+ case 0:
|
|
|
+ return <StepOne stepOneData={stepOneData} onSetStepOneData={setStepOneData} />;
|
|
|
+ case 1:
|
|
|
+ return (
|
|
|
+ <StepTwo
|
|
|
+ appStoreApps={appStoreApps}
|
|
|
+ stepTwoData={stepTwoData}
|
|
|
+ onSetStepTwoData={setStepTwoData}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ case 2:
|
|
|
+ return (
|
|
|
+ <StepThree stepThreeData={stepThreeData} onSetStepOneData={setStepThreeData} />
|
|
|
+ );
|
|
|
+ case 3:
|
|
|
+ return (
|
|
|
+ <StepFour
|
|
|
+ stepFourData={stepFourData}
|
|
|
+ onSetStepFourData={setStepFourData}
|
|
|
+ onStartItunesAuthentication={handleStartItunesAuthentication}
|
|
|
+ onStartSmsAuthentication={handleStartSmsAuthentication}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ case 4:
|
|
|
+ return (
|
|
|
+ <StepFifth
|
|
|
+ appleStoreOrgs={appleStoreOrgs}
|
|
|
+ stepFifthData={stepFifthData}
|
|
|
+ onSetStepFifthData={setStepFifthData}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ default:
|
|
|
+ return (
|
|
|
+ <Alert type="error" icon={<IconWarning />}>
|
|
|
+ {t('This step could not be found.')}
|
|
|
+ </Alert>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getAlerts() {
|
|
|
+ const alerts: React.ReactElement[] = [];
|
|
|
+
|
|
|
+ const appConnectStoreUpdateAlertMessage = getAppConnectStoreUpdateAlertMessage(
|
|
|
+ appStoreConnectContext ?? {}
|
|
|
+ );
|
|
|
+
|
|
|
if (
|
|
|
- !isEditing &&
|
|
|
- isEditingAppStoreCredentials &&
|
|
|
- isAppStoreCredentialsDataInvalid()
|
|
|
+ appConnectStoreUpdateAlertMessage ===
|
|
|
+ appStoreConnectAlertMessage.appStoreCredentialsInvalid &&
|
|
|
+ activeStep === 0
|
|
|
) {
|
|
|
- setIsEditingItunesCredentials(true);
|
|
|
+ alerts.push(
|
|
|
+ <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
+ {t(
|
|
|
+ 'Your App Store Connect credentials are invalid. To reconnect, update your credentials.'
|
|
|
+ )}
|
|
|
+ </StyledAlert>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- setIsEditingAppStoreCredentials(isEditing);
|
|
|
+ if (
|
|
|
+ appConnectStoreUpdateAlertMessage ===
|
|
|
+ appStoreConnectAlertMessage.iTunesSessionInvalid &&
|
|
|
+ activeStep < 3
|
|
|
+ ) {
|
|
|
+ alerts.push(
|
|
|
+ <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
+ {t(
|
|
|
+ 'Your iTunes session has expired. To reconnect, sign in with your Apple ID and password.'
|
|
|
+ )}
|
|
|
+ </StyledAlert>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ appConnectStoreUpdateAlertMessage ===
|
|
|
+ appStoreConnectAlertMessage.iTunesSessionInvalid &&
|
|
|
+ activeStep === 3
|
|
|
+ ) {
|
|
|
+ alerts.push(
|
|
|
+ <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
+ {t('Enter your authentication code to re-validate your iTunes session.')}
|
|
|
+ </StyledAlert>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ !appConnectStoreUpdateAlertMessage &&
|
|
|
+ revalidateItunesSession &&
|
|
|
+ activeStep === 0
|
|
|
+ ) {
|
|
|
+ alerts.push(
|
|
|
+ <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
+ {t('Your iTunes session has already been re-validated.')}
|
|
|
+ </StyledAlert>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return alerts;
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderBodyContent() {
|
|
|
+ const alerts = getAlerts();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Fragment>
|
|
|
+ {!!alerts.length && (
|
|
|
+ <Alerts marginBottom={activeStep === 3 ? 1.5 : 3}>
|
|
|
+ {alerts.map((alert, index) => (
|
|
|
+ <Fragment key={index}>{alert}</Fragment>
|
|
|
+ ))}
|
|
|
+ </Alerts>
|
|
|
+ )}
|
|
|
+ {renderCurrentStep()}
|
|
|
+ </Fragment>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
<Fragment>
|
|
|
- <Body>
|
|
|
- {revalidateItunesSession && !itunesSessionInvalid && (
|
|
|
- <StyledAlert type="warning" icon={<IconInfo />}>
|
|
|
- {t('Your iTunes session has already been re-validated.')}
|
|
|
- </StyledAlert>
|
|
|
- )}
|
|
|
- <StyledList symbol="colored-numeric">
|
|
|
- <Accordion summary={t('App Store Connect credentials')} defaultExpanded>
|
|
|
- {appStoreCredentialsInvalid && (
|
|
|
- <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
- {t(
|
|
|
- 'Your App Store Connect credentials are invalid. To reconnect, update your credentials.'
|
|
|
- )}
|
|
|
- </StyledAlert>
|
|
|
- )}
|
|
|
- <AppStoreCredentials
|
|
|
- api={api}
|
|
|
- orgSlug={orgSlug}
|
|
|
- projectSlug={projectSlug}
|
|
|
- data={appStoreCredentialsData}
|
|
|
- isUpdating={isUpdating}
|
|
|
- isEditing={isEditingAppStoreCredentials}
|
|
|
- onChange={setAppStoreCredentialsData}
|
|
|
- onReset={() => setAppStoreCredentialsData(appStoreCredentialsInitialData)}
|
|
|
- onEdit={handleEditAppStoreCredentials}
|
|
|
- />
|
|
|
- </Accordion>
|
|
|
- <Accordion
|
|
|
- summary={t('iTunes credentials')}
|
|
|
- defaultExpanded={
|
|
|
- isUpdating ||
|
|
|
- itunesSessionInvalid ||
|
|
|
- (isItunesCredentialsDataInvalid() && !isAppStoreCredentialsDataInvalid()) ||
|
|
|
- (!isEditingItunesCredentials && !isItunesCredentialsDataInvalid())
|
|
|
- }
|
|
|
- >
|
|
|
- {!revalidateItunesSession && itunesSessionInvalid && (
|
|
|
- <StyledAlert type="warning" icon={<IconWarning />}>
|
|
|
- {t(
|
|
|
- 'Your iTunes session has expired. To reconnect, sign in with your Apple ID and password'
|
|
|
+ <Header closeButton>
|
|
|
+ <HeaderContent>
|
|
|
+ <NumericSymbol>{activeStep + 1}</NumericSymbol>
|
|
|
+ <HeaderContentTitle>{steps[activeStep]}</HeaderContentTitle>
|
|
|
+ <StepsOverview>
|
|
|
+ {tct('[currentStep] of [totalSteps]', {
|
|
|
+ currentStep: activeStep + 1,
|
|
|
+ totalSteps: steps.length,
|
|
|
+ })}
|
|
|
+ </StepsOverview>
|
|
|
+ </HeaderContent>
|
|
|
+ </Header>
|
|
|
+ {initialData && appStoreConnectContext?.isLoading !== false ? (
|
|
|
+ <Body>
|
|
|
+ <LoadingIndicator />
|
|
|
+ </Body>
|
|
|
+ ) : (
|
|
|
+ <Fragment>
|
|
|
+ <Body>{renderBodyContent()}</Body>
|
|
|
+ <Footer>
|
|
|
+ <ButtonBar gap={1}>
|
|
|
+ {activeStep !== 0 && <Button onClick={handleGoBack}>{t('Back')}</Button>}
|
|
|
+ <StyledButton
|
|
|
+ priority="primary"
|
|
|
+ onClick={handleGoNext}
|
|
|
+ disabled={
|
|
|
+ isLoading ||
|
|
|
+ isFormInvalid() ||
|
|
|
+ (appStoreConnectContext
|
|
|
+ ? appStoreConnectContext?.isLoading !== false
|
|
|
+ : false)
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {isLoading && (
|
|
|
+ <LoadingIndicatorWrapper>
|
|
|
+ <LoadingIndicator mini />
|
|
|
+ </LoadingIndicatorWrapper>
|
|
|
)}
|
|
|
- </StyledAlert>
|
|
|
- )}
|
|
|
- <ItunesCredentials
|
|
|
- api={api}
|
|
|
- orgSlug={orgSlug}
|
|
|
- projectSlug={projectSlug}
|
|
|
- data={iTunesCredentialsData}
|
|
|
- isUpdating={isUpdating}
|
|
|
- isEditing={isEditingItunesCredentials}
|
|
|
- revalidateItunesSession={revalidateItunesSession && itunesSessionInvalid}
|
|
|
- onChange={setItunesCredentialsData}
|
|
|
- onReset={() => setItunesCredentialsData(iTunesCredentialsInitialData)}
|
|
|
- onEdit={setIsEditingItunesCredentials}
|
|
|
- />
|
|
|
- </Accordion>
|
|
|
- </StyledList>
|
|
|
- </Body>
|
|
|
- <Footer>
|
|
|
- <ButtonBar gap={1.5}>
|
|
|
- <Button onClick={closeModal}>{t('Cancel')}</Button>
|
|
|
- <StyledButton
|
|
|
- priority="primary"
|
|
|
- onClick={handleSave}
|
|
|
- disabled={isFormInvalid() || isLoading}
|
|
|
- >
|
|
|
- {t('Save')}
|
|
|
- </StyledButton>
|
|
|
- </ButtonBar>
|
|
|
- </Footer>
|
|
|
+ {activeStep + 1 === steps.length
|
|
|
+ ? initialData
|
|
|
+ ? t('Update')
|
|
|
+ : t('Save')
|
|
|
+ : steps[activeStep + 1]}
|
|
|
+ </StyledButton>
|
|
|
+ </ButtonBar>
|
|
|
+ </Footer>
|
|
|
+ </Fragment>
|
|
|
+ )}
|
|
|
</Fragment>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export default withApi(AppStoreConnect);
|
|
|
|
|
|
-const StyledList = styled(List)`
|
|
|
- grid-gap: ${space(2)};
|
|
|
- & > li {
|
|
|
- padding-left: 0;
|
|
|
- :before {
|
|
|
- z-index: 1;
|
|
|
- left: 9px;
|
|
|
- top: ${space(1.5)};
|
|
|
- }
|
|
|
- }
|
|
|
+const HeaderContent = styled('div')`
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: max-content max-content 1fr;
|
|
|
+ align-items: center;
|
|
|
+ grid-gap: ${space(1)};
|
|
|
+`;
|
|
|
+
|
|
|
+const NumericSymbol = styled('div')`
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ font-weight: 700;
|
|
|
+ font-size: ${p => p.theme.fontSizeMedium};
|
|
|
+ background-color: ${p => p.theme.yellow300};
|
|
|
+`;
|
|
|
+
|
|
|
+const HeaderContentTitle = styled('div')`
|
|
|
+ font-weight: 700;
|
|
|
+ font-size: ${p => p.theme.fontSizeExtraLarge};
|
|
|
+`;
|
|
|
+
|
|
|
+const StepsOverview = styled('div')`
|
|
|
+ color: ${p => p.theme.gray300};
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+`;
|
|
|
+
|
|
|
+const LoadingIndicatorWrapper = styled('div')`
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
`;
|
|
|
|
|
|
const StyledButton = styled(Button)`
|
|
|
position: relative;
|
|
|
`;
|
|
|
|
|
|
+const Alerts = styled('div')<{marginBottom: ValidSize}>`
|
|
|
+ display: grid;
|
|
|
+ grid-gap: ${space(1.5)};
|
|
|
+ margin-bottom: ${p => space(p.marginBottom)};
|
|
|
+`;
|
|
|
+
|
|
|
const StyledAlert = styled(Alert)`
|
|
|
- margin: ${space(1)} 0 ${space(2)} 0;
|
|
|
+ margin: 0;
|
|
|
`;
|