@squiz/resource-browser 3.0.1-rc.6 → 3.0.1-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -54,7 +54,7 @@ const ResourceBrowser = (props) => {
54
54
  const [isModalOpen, setIsModalOpen] = (0, react_1.useState)(false);
55
55
  const [source, setSource] = (0, react_1.useState)(null);
56
56
  const [mode, setMode] = (0, react_1.useState)(null);
57
- const { data: sources, isLoading, error: sourcesError } = (0, useSources_1.useSources)({ onRequestSources, plugins });
57
+ const { data: sources, isLoading, error: sourcesError, reload: reloadSources } = (0, useSources_1.useSources)({ onRequestSources, plugins });
58
58
  const [plugin, setPlugin] = (0, react_1.useState)(null);
59
59
  // MainContainer will render a list of sources of one is not provided to it, callback to allow it to set the source once a user selects
60
60
  const handleSourceSelect = (0, react_1.useCallback)((source, mode) => {
@@ -103,6 +103,10 @@ const ResourceBrowser = (props) => {
103
103
  setMode(null);
104
104
  }
105
105
  }, [sources, isModalOpen]);
106
+ // Reset function to allow user manual reload if sources fail in inline usage
107
+ const handleReset = (0, react_1.useCallback)(() => {
108
+ reloadSources();
109
+ }, [reloadSources]);
106
110
  // Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
107
111
  return (react_1.default.createElement("div", { className: "squiz-rb-scope" },
108
112
  react_1.default.createElement(Plugin_1.PluginRender, { key: "default", render: plugin === null, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: () => {
@@ -111,9 +115,9 @@ const ResourceBrowser = (props) => {
111
115
  error: null,
112
116
  isLoading: false,
113
117
  };
114
- }, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange }),
118
+ }, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }),
115
119
  plugins.map((thisPlugin) => {
116
- return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange }));
120
+ return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }));
117
121
  })));
118
122
  };
119
123
  exports.ResourceBrowser = ResourceBrowser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/resource-browser",
3
- "version": "3.0.1-rc.6",
3
+ "version": "3.0.1-rc.7",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -28,7 +28,8 @@
28
28
  "@react-types/shared": "^3.23.1",
29
29
  "@squiz/dx-json-schema-lib": "^1.67.0",
30
30
  "@squiz/generic-browser-lib": "1.67.2",
31
- "@squiz/resource-browser-ui-lib": "^1.0.0-rc.5",
31
+ "@squiz/resource-browser-ui-lib": "^1.0.0-rc.6",
32
+ "@squiz/sds": "^1.3.1",
32
33
  "clsx": "^2.1.0",
33
34
  "expiry-map": "^2.0.0",
34
35
  "p-memoize": "^4.0.4",
@@ -88,5 +89,5 @@
88
89
  "volta": {
89
90
  "node": "18.18.0"
90
91
  },
91
- "gitHead": "0488272dbc1a564329403dec744efb6fc958d3b2"
92
+ "gitHead": "b1ca30987b9284691e8d455792e65f13a32fdceb"
92
93
  }
@@ -40,6 +40,7 @@ describe('Plugin', () => {
40
40
  setSource: () => {},
41
41
  isModalOpen: false,
42
42
  onModalStateChange: () => {},
43
+ onRetry: () => {},
43
44
  };
44
45
  render(<PluginRender render={true} inline={false} {...props} />);
45
46
 
@@ -71,6 +72,7 @@ describe('Plugin', () => {
71
72
  setSource: () => {},
72
73
  isModalOpen: false,
73
74
  onModalStateChange: () => {},
75
+ onRetry: () => {},
74
76
  };
75
77
  render(<PluginRender render={true} inline={true} inlineType="image" {...props} />);
76
78
 
@@ -15,6 +15,7 @@ export type PluginRenderType = ResourceBrowserInputProps & {
15
15
  render: boolean;
16
16
  inline: boolean;
17
17
  inlineType?: InlineType;
18
+ onRetry: () => void;
18
19
  };
19
20
  export const PluginRender = ({ render, inline, inlineType, ...props }: PluginRenderType) => {
20
21
  if (render) {
@@ -0,0 +1,47 @@
1
+ import React, { act } from 'react';
2
+ import { render, waitFor, screen, fireEvent } from '@testing-library/react';
3
+ import { Error } from '@squiz/resource-browser-ui-lib';
4
+ import { InlineLoadingErrorState } from './InlineLoadingErrorState';
5
+
6
+ jest.mock('@squiz/resource-browser-ui-lib', () => {
7
+ return {
8
+ ...jest.requireActual('@squiz/resource-browser-ui-lib'),
9
+ Error: jest.fn(() => <div></div>),
10
+ };
11
+ });
12
+
13
+ describe('InlineLoadingErrorState', () => {
14
+ const defaultProps = {
15
+ isLoading: false,
16
+ error: undefined,
17
+ };
18
+
19
+ it('clicking close button should call the onClose callback', async () => {
20
+ const onClose = jest.fn();
21
+
22
+ //@ts-ignore
23
+ render(<InlineLoadingErrorState {...defaultProps} title="Select Asset" onClose={onClose} />);
24
+
25
+ await act(() => fireEvent.click(screen.getByRole('button', { name: 'Close Select Asset dialog' })));
26
+ expect(onClose).toHaveBeenCalled();
27
+ });
28
+
29
+ it('Will render loading on isLoading === true', async () => {
30
+ //@ts-ignore
31
+ render(<InlineLoadingErrorState {...defaultProps} isLoading={true} />);
32
+
33
+ await waitFor(() => {
34
+ expect(screen.getByText('Loading...')).toBeTruthy();
35
+ });
36
+ });
37
+
38
+ it('Will render error on error !== null', async () => {
39
+ const onRetry = jest.fn();
40
+ //@ts-ignore
41
+ render(<InlineLoadingErrorState {...defaultProps} onRetry={onRetry} error={new Error('test')} />);
42
+
43
+ await waitFor(() => {
44
+ expect(Error).toHaveBeenCalledWith(expect.objectContaining({ onReset: onRetry }), {});
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { DOMAttributes, FocusableElement } from '@react-types/shared';
3
+ import { Error } from '@squiz/resource-browser-ui-lib';
4
+ import { Spinner } from '@squiz/sds';
5
+
6
+ export type InlineLoadingErrorStateProps = {
7
+ title: string;
8
+ titleAriaProps: DOMAttributes<FocusableElement>;
9
+ onClose: () => void;
10
+ onRetry: () => void;
11
+
12
+ isLoading: boolean;
13
+ error: Error | null;
14
+ };
15
+
16
+ export const InlineLoadingErrorState = ({ title, titleAriaProps, onClose, onRetry, isLoading, error }: InlineLoadingErrorStateProps) => {
17
+ return (
18
+ <div className="relative flex flex-col h-full text-gray-800">
19
+ <div className="flex items-center py-3 pl-6 pr-10 min-h-[68px]">
20
+ <h2 {...titleAriaProps} className="text-xl leading-6 text-gray-800 font-semibold mr-6">
21
+ {title}
22
+ </h2>
23
+ <button
24
+ type="button"
25
+ aria-label={`Close ${title} dialog`}
26
+ onClick={onClose}
27
+ className="absolute top-2 right-2 p-2.5 rounded hover:bg-blue-100 focus:bg-blue-100"
28
+ >
29
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
30
+ <path
31
+ d="M13.3 0.710017C13.1131 0.522765 12.8595 0.417532 12.595 0.417532C12.3305 0.417532 12.0768 0.522765 11.89 0.710017L6.99997 5.59002L2.10997 0.700017C1.92314 0.512765 1.66949 0.407532 1.40497 0.407532C1.14045 0.407532 0.886802 0.512765 0.699971 0.700017C0.309971 1.09002 0.309971 1.72002 0.699971 2.11002L5.58997 7.00002L0.699971 11.89C0.309971 12.28 0.309971 12.91 0.699971 13.3C1.08997 13.69 1.71997 13.69 2.10997 13.3L6.99997 8.41002L11.89 13.3C12.28 13.69 12.91 13.69 13.3 13.3C13.69 12.91 13.69 12.28 13.3 11.89L8.40997 7.00002L13.3 2.11002C13.68 1.73002 13.68 1.09002 13.3 0.710017Z"
32
+ fill="currentColor"
33
+ />
34
+ </svg>
35
+ </button>
36
+ </div>
37
+ <div className="border-t border-gray-300 overflow-y-hidden">
38
+ <div className="w-screen max-w-[400px] min-h-[320px] flex-1 grow-[3] border-r border-gray-300 bg-gray-100 pl-6 pr-6 pb-6 pt-4 flex justify-center items-center text-gray-500">
39
+ {isLoading && <Spinner type="lg" />}
40
+
41
+ {!isLoading && error && (
42
+ <Error
43
+ onReset={onRetry}
44
+ infoText={
45
+ 'There was a problem loading this feature. If this problem persists, contact your administrator or support team.'
46
+ }
47
+ buttonText={'Try again'}
48
+ />
49
+ )}
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ };
@@ -1,10 +1,15 @@
1
1
  import React from 'react';
2
- import { render, waitFor, screen } from '@testing-library/react';
2
+ import { render, waitFor, screen, fireEvent } from '@testing-library/react';
3
3
 
4
4
  import { ResourceBrowserInlineButton } from './ResourceBrowserInlineButton';
5
5
 
6
+ import * as ILES from './InlineLoadingErrorState/InlineLoadingErrorState';
7
+ jest.spyOn(ILES, 'InlineLoadingErrorState');
8
+
6
9
  describe('ResourceBrowserInlineButton', () => {
7
10
  const defaultProps = {
11
+ isLoading: false,
12
+ error: undefined,
8
13
  useResource: () => {
9
14
  return {
10
15
  data: null,
@@ -30,4 +35,44 @@ describe('ResourceBrowserInlineButton', () => {
30
35
  expect(screen.getByLabelText('change link')).toBeTruthy();
31
36
  });
32
37
  });
38
+
39
+ it('On trigger press render loading if still awaiting external data', async () => {
40
+ //@ts-ignore
41
+ render(<ResourceBrowserInlineButton {...defaultProps} inlineType={'image'} isLoading={true} />);
42
+
43
+ const button = screen.getByRole('button');
44
+ fireEvent.click(button);
45
+
46
+ await waitFor(() => {
47
+ expect(ILES.InlineLoadingErrorState).toHaveBeenCalledWith(expect.objectContaining({ isLoading: true }), {});
48
+ });
49
+ });
50
+
51
+ it('On trigger press render error if external error provided', async () => {
52
+ const error = new Error('test');
53
+ //@ts-ignore
54
+ render(<ResourceBrowserInlineButton {...defaultProps} inlineType={'image'} error={error} />);
55
+
56
+ const button = screen.getByRole('button');
57
+ fireEvent.click(button);
58
+
59
+ await waitFor(() => {
60
+ expect(ILES.InlineLoadingErrorState).toHaveBeenCalledWith(expect.objectContaining({ error }), {});
61
+ });
62
+ });
63
+
64
+ it('On trigger press render loading if still awaiting resource data', async () => {
65
+ const useResource = () => {
66
+ return { data: null, isLoading: true };
67
+ };
68
+ //@ts-ignore
69
+ render(<ResourceBrowserInlineButton {...defaultProps} inlineType={'image'} useResource={useResource} />);
70
+
71
+ const button = screen.getByRole('button');
72
+ fireEvent.click(button);
73
+
74
+ await waitFor(() => {
75
+ expect(ILES.InlineLoadingErrorState).toHaveBeenCalledWith(expect.objectContaining({ isLoading: true }), {});
76
+ });
77
+ });
33
78
  });
@@ -2,12 +2,14 @@ import React from 'react';
2
2
  import MainContainer from '../MainContainer/MainContainer';
3
3
  import { ResourceBrowserInputProps } from '../ResourceBrowserInput/ResourceBrowserInput';
4
4
  import { ModalTrigger } from '@squiz/resource-browser-ui-lib';
5
+ import { InlineLoadingErrorState } from './InlineLoadingErrorState/InlineLoadingErrorState';
5
6
  import { ImageInline } from '../Icons/ImageInline';
6
7
  import { LinkInline } from '../Icons/LinkInline';
7
8
  import { InlineType } from '../types';
8
9
 
9
10
  export type ResourceBrowserInlineButtonProps = ResourceBrowserInputProps & {
10
11
  inlineType: InlineType;
12
+ onRetry: () => void;
11
13
  };
12
14
 
13
15
  export const ResourceBrowserInlineButton = ({
@@ -15,6 +17,7 @@ export const ResourceBrowserInlineButton = ({
15
17
  modalTitle,
16
18
  allowedTypes,
17
19
  onChange,
20
+ onRetry,
18
21
  value,
19
22
  useResource,
20
23
  isDisabled,
@@ -29,7 +32,8 @@ export const ResourceBrowserInlineButton = ({
29
32
  isModalOpen,
30
33
  onModalStateChange,
31
34
  }: ResourceBrowserInlineButtonProps) => {
32
- const { data: resource, error: resourceError, isLoading: isResourceLoading } = useResource(value?.resourceId || null, source);
35
+ // If an error happens loading the resource the inline browser just opens without it as if no preselectedResource is provided
36
+ const { data: resource, isLoading: isResourceLoading } = useResource(value?.resourceId || null, source);
33
37
 
34
38
  const isImagePicker = inlineType === 'image';
35
39
 
@@ -48,20 +52,34 @@ export const ResourceBrowserInlineButton = ({
48
52
  containerClasses="inline-launch-button__button"
49
53
  >
50
54
  {(onClose, titleProps) => (
51
- <MainContainer
52
- selectedSource={source}
53
- sources={sources}
54
- preselectedResource={resource}
55
- plugin={plugin}
56
- pluginMode={pluginMode}
57
- searchEnabled={searchEnabled}
58
- title={modalTitle}
59
- titleAriaProps={titleProps}
60
- allowedTypes={allowedTypes}
61
- onSourceSelect={setSource}
62
- onClose={onClose}
63
- onChange={onChange}
64
- />
55
+ <>
56
+ {(isLoading || isResourceLoading || error) && (
57
+ <InlineLoadingErrorState
58
+ title={modalTitle}
59
+ titleAriaProps={titleProps}
60
+ onClose={onClose}
61
+ onRetry={onRetry}
62
+ isLoading={isLoading || isResourceLoading}
63
+ error={error}
64
+ />
65
+ )}
66
+ {!(isLoading || isResourceLoading) && !error && (
67
+ <MainContainer
68
+ selectedSource={source}
69
+ sources={sources}
70
+ preselectedResource={resource}
71
+ plugin={plugin}
72
+ pluginMode={pluginMode}
73
+ searchEnabled={searchEnabled}
74
+ title={modalTitle}
75
+ titleAriaProps={titleProps}
76
+ allowedTypes={allowedTypes}
77
+ onSourceSelect={setSource}
78
+ onClose={onClose}
79
+ onChange={onChange}
80
+ />
81
+ )}
82
+ </>
65
83
  )}
66
84
  </ModalTrigger>
67
85
  <div className="inline-launch-button__label">{isImagePicker ? `Change image` : `Change link`}</div>
@@ -11,7 +11,7 @@ type CreateCallbacksProps = Partial<{
11
11
  error?: string;
12
12
  }>;
13
13
 
14
- export const createPlugins = (callbackWait: number, headerPortal = false): ResourceBrowserPlugin[] => {
14
+ export const createPlugins = (callbackWait: number, headerPortal = false, resourceError?: boolean): ResourceBrowserPlugin[] => {
15
15
  const types = ['dam', 'matrix'];
16
16
  return types.map((type): ResourceBrowserPlugin => {
17
17
  return {
@@ -20,6 +20,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
20
20
  createHeaderPortal: headerPortal,
21
21
  sourceBrowserComponent: () => {
22
22
  return (props) => {
23
+ console.log('sourceBrowserComponent invoked with', props);
23
24
  return (
24
25
  <div className="h-screen lg:h-[calc(100vh-9rem)] w-screen max-w-[52rem]">
25
26
  <div>THIS IS A {type} BROWSE PLUGIN</div>
@@ -40,6 +41,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
40
41
  },
41
42
  sourceSearchComponent: () => {
42
43
  return (props) => {
44
+ console.log('sourceSearchComponent invoked with', props);
43
45
  return (
44
46
  <div className="h-screen lg:h-[calc(100vh-9rem)] w-screen max-w-[52rem]">
45
47
  <div>THIS IS A {type} SEARCH PLUGIN</div>
@@ -108,9 +110,14 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
108
110
  },
109
111
  useResolveResource: (unresolvedResource) => {
110
112
  const [resource, setResource] = useState<ResourceBrowserResource | null>(null);
111
- const [error, setError] = useState<Error | null>(null);
113
+ const [error, setError] = useState<Error | null>(
114
+ resourceError ? new Error(`Error loading: ${JSON.stringify(unresolvedResource)}`) : null,
115
+ );
112
116
  const [isLoading, setIsLoading] = useState<boolean>(false);
113
117
 
118
+ if (resourceError) {
119
+ console.log('useResolveResource has returned an error');
120
+ }
114
121
  useEffect(() => {
115
122
  if (unresolvedResource !== null) {
116
123
  setTimeout(() => {
@@ -144,7 +151,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false): Resou
144
151
  }, [unresolvedResource, setResource, setIsLoading]);
145
152
 
146
153
  return {
147
- data: resource,
154
+ data: !error ? resource : null,
148
155
  error: error,
149
156
  isLoading: isLoading,
150
157
  };
@@ -161,10 +168,11 @@ export const createResourceBrowserCallbacks = ({
161
168
  }: CreateCallbacksProps = {}) => {
162
169
  return {
163
170
  onRequestSources: () => {
171
+ console.log(`onRequestSources invoked`);
164
172
  return new Promise((resolve, reject) => {
165
173
  if (!sourceIsLoading) {
166
174
  setTimeout(() => {
167
- if (error && Math.random() > 0.5) {
175
+ if (error) {
168
176
  reject(new Error(error));
169
177
  } else {
170
178
  resolve(singleSource ? [sampleSources[0]] : sampleSources);
package/src/index.scss CHANGED
@@ -3,7 +3,9 @@
3
3
  @import 'tailwindcss/components';
4
4
  @import 'tailwindcss/utilities';
5
5
 
6
+ @import '@squiz/sds/lib/package.css';
6
7
  @import '@squiz/sds/lib/styles/_imports.scss';
8
+ @import '@squiz/resource-browser-ui-lib/src/index';
7
9
 
8
10
  // Components
9
11
  @import './ResourcePicker/resource-picker';
@@ -11,8 +11,9 @@ export default {
11
11
  const Template: StoryFn<typeof ResourceBrowser> = (props) => {
12
12
  const [resource, setResource] = useState<ResourceBrowserUnresolvedResource | null>(props?.value || null);
13
13
  const { onRequestSources } = createResourceBrowserCallbacks({
14
- sourceIsLoading: false,
14
+ sourceIsLoading: props.sourceIsLoading || false,
15
15
  singleSource: props.singleSource,
16
+ error: props.sourceError ? 'Error' : undefined,
16
17
  });
17
18
 
18
19
  const onChange = (resource: ResourceBrowserResource | null) => {
@@ -32,7 +33,7 @@ const Template: StoryFn<typeof ResourceBrowser> = (props) => {
32
33
  }
33
34
  };
34
35
 
35
- const plugins: ResourceBrowserPlugin[] = createPlugins(props.callbackWait, props.headerPortal);
36
+ const plugins: ResourceBrowserPlugin[] = createPlugins(props.callbackWait, props.headerPortal, props.resourceError);
36
37
 
37
38
  return (
38
39
  <div className="w-[400px] m-3">
@@ -54,6 +55,7 @@ export const Primary = Template.bind({});
54
55
  Primary.args = {
55
56
  modalTitle: 'Choose asset',
56
57
  sourceIsLoading: false,
58
+ sourceIsError: false,
57
59
  resourceIsLoading: false,
58
60
  error: '',
59
61
  callbackWait: 0,
@@ -100,3 +102,21 @@ InlineEditSelected.args = {
100
102
  sourceId: 'c90feac1-55f3-4e1f-9b56-c22829e3f510',
101
103
  },
102
104
  };
105
+
106
+ export const InlineEditLoading = Template.bind({});
107
+ InlineEditLoading.args = {
108
+ ...InlineEditSelected.args,
109
+ sourceIsLoading: true,
110
+ };
111
+
112
+ export const InlineEditError = Template.bind({});
113
+ InlineEditError.args = {
114
+ ...InlineEditSelected.args,
115
+ sourceError: true,
116
+ };
117
+
118
+ export const InlineEditResourceError = Template.bind({});
119
+ InlineEditResourceError.args = {
120
+ ...InlineEditSelected.args,
121
+ resourceError: true,
122
+ };
package/src/index.tsx CHANGED
@@ -48,7 +48,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
48
48
  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
49
49
  const [source, setSource] = useState<ResourceBrowserSourceWithPlugin | null>(null);
50
50
  const [mode, setMode] = useState<PluginLaunchMode | null>(null);
51
- const { data: sources, isLoading, error: sourcesError } = useSources({ onRequestSources, plugins });
51
+ const { data: sources, isLoading, error: sourcesError, reload: reloadSources } = useSources({ onRequestSources, plugins });
52
52
  const [plugin, setPlugin] = useState<ResourceBrowserPlugin | null>(null);
53
53
 
54
54
  // MainContainer will render a list of sources of one is not provided to it, callback to allow it to set the source once a user selects
@@ -109,6 +109,11 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
109
109
  }
110
110
  }, [sources, isModalOpen]);
111
111
 
112
+ // Reset function to allow user manual reload if sources fail in inline usage
113
+ const handleReset = useCallback(() => {
114
+ reloadSources();
115
+ }, [reloadSources]);
116
+
112
117
  // Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
113
118
  return (
114
119
  <div className="squiz-rb-scope">
@@ -135,6 +140,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
135
140
  }}
136
141
  isModalOpen={isModalOpen}
137
142
  onModalStateChange={handleModalStateChange}
143
+ onRetry={handleReset}
138
144
  />
139
145
  {plugins.map((thisPlugin) => {
140
146
  return (
@@ -148,13 +154,14 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
148
154
  sources={sources}
149
155
  setSource={handleSourceSelect}
150
156
  isLoading={isLoading}
151
- error={error}
157
+ error={sourcesError || error}
152
158
  plugin={plugin}
153
159
  pluginMode={mode}
154
160
  searchEnabled={searchEnabled}
155
161
  useResource={thisPlugin.useResolveResource}
156
162
  isModalOpen={isModalOpen}
157
163
  onModalStateChange={handleModalStateChange}
164
+ onRetry={handleReset}
158
165
  />
159
166
  );
160
167
  })}