diff --git a/README.md b/README.md
index b3253ed0..c1062709 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,9 @@ npm run start
Upon installation, configure the app using an API key generated via the imgix [Dashboard](https://dashboard.imgix.com/api-keys). **Ensure that the generated key has the following permissions: `Sources` and `Asset Manager Browse`.**
+> [!TIP]
+> You can also optionally configure a default Source ID for the app to use. When configured, you'll have to click the Sources dropdown to select an asset from a different source.
+
Following the instructions on the screen, enter in the API key and press `Verify`. If the key is valid, you will receive a notification that the key has been successfully verified. If verification fails, you will need to ensure that the key was entered correctly.
@@ -382,4 +385,3 @@ export const query = graphql`
}
`;
```
-
diff --git a/src/components/ConfigScreen/ConfigScreen.tsx b/src/components/ConfigScreen/ConfigScreen.tsx
index 8d4a8159..03accf26 100644
--- a/src/components/ConfigScreen/ConfigScreen.tsx
+++ b/src/components/ConfigScreen/ConfigScreen.tsx
@@ -6,9 +6,7 @@ import {
Workbench,
Paragraph,
TextField,
- Notification,
Icon,
- Button,
TextLink,
List,
ListItem,
@@ -16,13 +14,13 @@ import {
Subheading,
} from '@contentful/forma-36-react-components';
import ImgixAPI, { APIError } from 'imgix-management-js';
-import debounce from 'lodash.debounce';
import './ConfigScreen.css';
import packageJson from './../../../package.json';
export interface AppInstallationParameters {
imgixAPIKey?: string;
+ sourceID?: string;
successfullyVerified?: boolean;
}
@@ -144,17 +142,44 @@ export default class Config extends Component {
}
onConfigure = async () => {
+ this.setState({
+ ...this.state,
+ validationMessage: '',
+ parameters: {
+ ...this.state.parameters,
+ successfullyVerified: false,
+ },
+ });
// This method will be called when a user clicks on "Install"
// or "Save" in the configuration screen.
// for more details see https://www.contentful.com/developers/docs/extensibility/ui-extensions/sdk-reference/#register-an-app-configuration-hook
// ensure the API key is validated
- await this.verifyAPIKey();
+ const hasValidAPIKey = await this.verifyAPIKey();
+
+ if (!hasValidAPIKey) {
+ const validationMessage =
+ "We couldn't verify this API Key. Confirm your details and try again.";
+ this.setState({
+ ...this.state,
+ validationMessage,
+ });
+
+ return false; // return false so we don't save invalid config details
+ }
// Generate a new target state with the App assigned to the selected
// content types
const targetState = await this.createTargetState();
+ this.setState({
+ ...this.state,
+ parameters: {
+ ...this.state.parameters,
+ successfullyVerified: true,
+ },
+ });
+
return {
// Parameters to be persisted as the app configuration.
parameters: this.state.parameters,
@@ -197,16 +222,36 @@ export default class Config extends Component {
return { ...currentState, EditorInterface };
};
- handleChange = (e: ChangeEvent) => {
+ handleAPIKeyChange = (
+ e: ChangeEvent,
+ ) => {
+ const prevState = { ...this.state };
this.setState({
+ ...prevState,
+ validationMessage: '',
parameters: {
+ ...prevState.parameters,
imgixAPIKey: e.target.value,
successfullyVerified: this.state.parameters.successfullyVerified,
},
});
};
- verifyAPIKey = async () => {
+ handleSourceIDChange = (
+ e: ChangeEvent,
+ ) => {
+ const prevState = { ...this.state };
+ this.setState({
+ ...prevState,
+ parameters: {
+ ...prevState.parameters,
+ sourceID: e.target.value,
+ successfullyVerified: this.state.parameters.successfullyVerified,
+ },
+ });
+ };
+
+ verifyAPIKey = async (): Promise => {
this.setState({ isButtonLoading: true });
const imgix = new ImgixAPI({
@@ -214,21 +259,18 @@ export default class Config extends Component {
pluginOrigin: `contentful/v${packageJson.version}`,
});
- let updatedInstallationParameters: AppInstallationParameters = {
- ...this.state.parameters,
- };
-
try {
- await imgix.request('sources');
- Notification.setPosition('top', { offset: 650 });
- Notification.success(
- 'Your API key was successfully confirmed! Click the Install/Save button (in the top right corner) to complete installation.',
- {
- duration: 10000,
- id: 'ix-config-notification',
- },
- );
- updatedInstallationParameters.successfullyVerified = true;
+ if (this.state.parameters.sourceID?.length) {
+ await imgix.request(`sources/${this.state.parameters.sourceID}`);
+ } else {
+ await imgix.request('sources');
+ }
+ this.setState({
+ ...this.state,
+ isButtonLoading: false,
+ });
+
+ return true;
} catch (error) {
// APIError will emit more helpful data for debugging
if (error instanceof APIError) {
@@ -236,43 +278,15 @@ export default class Config extends Component {
} else {
console.error(error);
}
- Notification.setPosition('top', { offset: 650 });
- Notification.error(
- "We couldn't verify this API Key. Confirm your details and try again.",
- {
- duration: 3000,
- id: 'ix-config-notification',
- },
- );
- updatedInstallationParameters.successfullyVerified = false;
- } finally {
this.setState({
- validationMessage: '',
+ ...this.state,
isButtonLoading: false,
- parameters: updatedInstallationParameters,
});
- }
- };
- onClick = async () => {
- if (this.state.parameters.imgixAPIKey === '') {
- let updatedInstallationParameters: AppInstallationParameters = {
- ...this.state.parameters,
- };
- updatedInstallationParameters.successfullyVerified = false;
- this.setState({
- validationMessage: 'Please input your API Key',
- parameters: updatedInstallationParameters,
- });
- } else {
- await this.verifyAPIKey();
+ return false;
}
};
- debounceOnClick = debounce(this.onClick, 1000, {
- leading: true,
- });
-
getAPIKey = async () => {
return this.props.sdk.app
.getParameters()
@@ -347,7 +361,7 @@ export default class Config extends Component {
type: 'password',
autoComplete: 'new-api-key',
}}
- onChange={this.handleChange}
+ onChange={this.handleAPIKeyChange}
/>
{this.state.parameters.successfullyVerified && (
@@ -366,16 +380,22 @@ export default class Config extends Component {
https://dashboard.imgix.com/api-keys
+
-
{this.state.contentTypes.length > 0 && (
diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx
index c59756b3..c8908689 100644
--- a/src/components/Dialog/Dialog.tsx
+++ b/src/components/Dialog/Dialog.tsx
@@ -250,15 +250,21 @@ export default class Dialog extends Component
{
if (sources.length === 0) {
throw noSourcesError();
}
+ const invocationParams = this.props.sdk.parameters
+ .invocation as AppInvocationParameters;
+ const installationParams = this.props.sdk.parameters
+ .installation as AppInstallationParameters;
+
// check if previously selected source exists
// if it does, add it to state.
- const previouslySelectedSource = (
- this.props.sdk.parameters.invocation as AppInvocationParameters
- )?.selectedImage?.selectedSource;
+ const previouslySelectedSourceID =
+ invocationParams?.selectedImage?.selectedSource?.id ||
+ installationParams.sourceID;
const selectedSource = sources.find((source: any) => {
- return source.id === previouslySelectedSource?.id;
+ return source.id === previouslySelectedSourceID;
});
+
if (selectedSource) {
this.setState({ allSources: sources, selectedSource });
} else {
diff --git a/src/components/Gallery/ImageGallery.tsx b/src/components/Gallery/ImageGallery.tsx
index 6d91ccaa..3c42efd1 100644
--- a/src/components/Gallery/ImageGallery.tsx
+++ b/src/components/Gallery/ImageGallery.tsx
@@ -108,7 +108,12 @@ export class Gallery extends Component {
) : // If the source is a webfolder
this.props.selectedSource.type === 'webfolder' ? (
diff --git a/src/components/SourceSelect/SourceSelectDropdown.tsx b/src/components/SourceSelect/SourceSelectDropdown.tsx
index acfd50d7..8eff87c9 100644
--- a/src/components/SourceSelect/SourceSelectDropdown.tsx
+++ b/src/components/SourceSelect/SourceSelectDropdown.tsx
@@ -5,7 +5,6 @@ import {
Dropdown,
DropdownList,
DropdownListItem,
- Paragraph,
Spinner,
} from '@contentful/forma-36-react-components';
@@ -42,24 +41,30 @@ export function SourceSelectDropdown({
isOpen={isOpen}
onClose={() => setOpen(false)}
toggleElement={
-
+ !allSources.length ? (
+
+ ) : (
+
+ )
}
>
- {!allSources.length ? (
-
- Loading
-
- ) : null}
{allSources.map((source: SourceProps) => (
handleClick(source)}>
{source.name}