@strapi/admin 4.2.0-alpha.7 → 4.2.0-alpha.8
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/admin/src/assets/images/icon_made-by-strapi.svg +5 -0
- package/admin/src/components/PrivateRoute/index.js +23 -17
- package/admin/src/hooks/index.js +0 -1
- package/admin/src/hooks/useFetchInstalledPlugins/index.js +23 -0
- package/admin/src/{pages/InstalledPluginsPage → hooks/useFetchInstalledPlugins}/utils/api.js +2 -4
- package/admin/src/hooks/useFetchMarketplacePlugins/index.js +23 -0
- package/admin/src/hooks/useFetchMarketplacePlugins/utils/api.js +17 -0
- package/admin/src/pages/AuthPage/index.js +22 -5
- package/admin/src/pages/InstalledPluginsPage/Plugins.js +6 -15
- package/admin/src/pages/MarketplacePage/components/EmptyPluginSearch/EmptyPluginGrid.js +27 -0
- package/admin/src/pages/MarketplacePage/components/EmptyPluginSearch/index.js +30 -0
- package/admin/src/pages/MarketplacePage/components/PluginCard/index.js +186 -0
- package/admin/src/pages/MarketplacePage/index.js +199 -107
- package/admin/src/pages/MarketplacePage/utils/api.js +9 -0
- package/admin/src/translations/en.json +16 -13
- package/build/4362.91a4253f.chunk.js +1 -0
- package/build/90f49a385afb000fb1d4.svg +5 -0
- package/build/{Admin-authenticatedApp.0b6b5c7e.chunk.js → Admin-authenticatedApp.187bd266.chunk.js} +1 -1
- package/build/Admin_marketplace.89a0a014.chunk.js +1 -0
- package/build/Admin_pluginsPage.97a514db.chunk.js +1 -0
- package/build/admin-users.2740c223.chunk.js +1 -0
- package/build/en-json.73a610d6.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/{main.1f025df1.js → main.c12537d5.js} +2 -2
- package/build/{main.1f025df1.js.LICENSE.txt → main.c12537d5.js.LICENSE.txt} +0 -0
- package/build/{runtime~main.53294538.js → runtime~main.e5240b25.js} +1 -1
- package/package.json +5 -5
- package/server/controllers/admin.js +12 -1
- package/.env +0 -0
- package/admin/src/hooks/useFetchPluginsFromMarketPlace/index.js +0 -49
- package/admin/src/pages/MarketplacePage/MarketplaceBanner/Wrapper.js +0 -28
- package/admin/src/pages/MarketplacePage/MarketplaceBanner/index.js +0 -37
- package/admin/src/pages/MarketplacePage/PluginCard/Wrapper.js +0 -148
- package/admin/src/pages/MarketplacePage/PluginCard/index.js +0 -185
- package/admin/src/pages/MarketplacePage/Wrapper.js +0 -5
- package/admin/src/pages/MarketplacePage/assets/marketplace-coming-soon.png +0 -0
- package/build/01a600d9e6e0dea21e33.png +0 -0
- package/build/4362.ce36d91a.chunk.js +0 -1
- package/build/Admin_marketplace.419010d8.chunk.js +0 -1
- package/build/Admin_pluginsPage.7d1bd7ce.chunk.js +0 -1
- package/build/admin-users.1fda1f27.chunk.js +0 -1
- package/build/en-json.98c7c4be.chunk.js +0 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect x="3" y="3" width="18" height="18" rx="4" fill="#4945FF"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8075 7.625H9.03058V11.1792H12.2533C12.4977 11.1792 12.6958 11.3773 12.6958 11.6216V14.8444H16.25V8.06746C16.25 7.82309 16.0519 7.625 15.8075 7.625Z" fill="white"/>
|
|
4
|
+
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M9.0308 7.625V11.1792H6.01073C5.81364 11.1792 5.71494 10.9409 5.8543 10.8015L9.0308 7.625ZM13.0735 18.0209C12.9342 18.1603 12.6959 18.0616 12.6959 17.8645V14.8444H16.25L13.0735 18.0209ZM12.4746 11.1792H9.03058V14.4019C9.03058 14.6463 9.22868 14.8444 9.47304 14.8444H12.6958V11.4004C12.6958 11.2782 12.5968 11.1792 12.4746 11.1792Z" fill="#DAD9FF"/>
|
|
5
|
+
</svg>
|
|
@@ -8,28 +8,34 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import React, { memo } from 'react';
|
|
11
|
-
import { Redirect, Route } from 'react-router-dom';
|
|
11
|
+
import { Redirect, Route, useLocation } from 'react-router-dom';
|
|
12
12
|
import PropTypes from 'prop-types';
|
|
13
13
|
import { auth } from '@strapi/helper-plugin';
|
|
14
14
|
|
|
15
15
|
/* eslint-disable react/jsx-curly-newline */
|
|
16
16
|
|
|
17
|
-
const PrivateRoute = ({ component: Component, path, ...rest }) =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
|
18
|
+
const { pathname, search } = useLocation();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Route
|
|
22
|
+
path={path}
|
|
23
|
+
render={props =>
|
|
24
|
+
auth.getToken() !== null ? (
|
|
25
|
+
<Component {...rest} {...props} />
|
|
26
|
+
) : (
|
|
27
|
+
<Redirect
|
|
28
|
+
to={{
|
|
29
|
+
pathname: '/auth/login',
|
|
30
|
+
search:
|
|
31
|
+
pathname !== '/' && `?redirectTo=${encodeURIComponent(`${pathname}${search}`)}`,
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
33
39
|
|
|
34
40
|
PrivateRoute.propTypes = {
|
|
35
41
|
component: PropTypes.any.isRequired,
|
package/admin/src/hooks/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { default as useConfigurations } from './useConfigurations';
|
|
2
2
|
export { default as useModels } from './useModels';
|
|
3
3
|
export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout';
|
|
4
|
-
export { default as useFetchPluginsFromMarketPlace } from './useFetchPluginsFromMarketPlace';
|
|
5
4
|
export { default as useFetchRole } from './useFetchRole';
|
|
6
5
|
export { default as useMenu } from './useMenu';
|
|
7
6
|
export { default as useRolesList } from './useRolesList';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useQuery } from 'react-query';
|
|
2
|
+
import { useNotification } from '@strapi/helper-plugin';
|
|
3
|
+
import { fetchInstalledPlugins } from './utils/api';
|
|
4
|
+
|
|
5
|
+
const useFetchInstalledPlugins = notifyLoad => {
|
|
6
|
+
const toggleNotification = useNotification();
|
|
7
|
+
|
|
8
|
+
return useQuery('list-installed-plugins', () => fetchInstalledPlugins(), {
|
|
9
|
+
onSuccess: () => {
|
|
10
|
+
if (notifyLoad) {
|
|
11
|
+
notifyLoad();
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
onError: () => {
|
|
15
|
+
toggleNotification({
|
|
16
|
+
type: 'warning',
|
|
17
|
+
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default useFetchInstalledPlugins;
|
package/admin/src/{pages/InstalledPluginsPage → hooks/useFetchInstalledPlugins}/utils/api.js
RENAMED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { axiosInstance } from '../../../core/utils';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fetchInstalledPlugins = async () => {
|
|
4
4
|
const { data } = await axiosInstance.get('/admin/plugins');
|
|
5
5
|
|
|
6
|
-
notify();
|
|
7
|
-
|
|
8
6
|
return data;
|
|
9
7
|
};
|
|
10
8
|
|
|
11
|
-
export {
|
|
9
|
+
export { fetchInstalledPlugins };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useQuery } from 'react-query';
|
|
2
|
+
import { useNotification } from '@strapi/helper-plugin';
|
|
3
|
+
import { fetchMarketplacePlugins } from './utils/api';
|
|
4
|
+
|
|
5
|
+
const useFetchMarketplacePlugins = notifyLoad => {
|
|
6
|
+
const toggleNotification = useNotification();
|
|
7
|
+
|
|
8
|
+
return useQuery('list-marketplace-plugins', () => fetchMarketplacePlugins(), {
|
|
9
|
+
onSuccess: () => {
|
|
10
|
+
if (notifyLoad) {
|
|
11
|
+
notifyLoad();
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
onError: () => {
|
|
15
|
+
toggleNotification({
|
|
16
|
+
type: 'warning',
|
|
17
|
+
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default useFetchMarketplacePlugins;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
const MARKETPLACE_API_URL = 'https://market-api.strapi.io';
|
|
4
|
+
|
|
5
|
+
const fetchMarketplacePlugins = async () => {
|
|
6
|
+
const { data: response } = await axios.get(`${MARKETPLACE_API_URL}/plugins`);
|
|
7
|
+
|
|
8
|
+
// Only keep v4 plugins
|
|
9
|
+
const filteredResponse = {
|
|
10
|
+
...response,
|
|
11
|
+
data: response.data.filter(plugin => plugin.attributes.strapiCompatibility === 'v4'),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return filteredResponse;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { fetchMarketplacePlugins };
|
|
@@ -14,7 +14,10 @@ import init from './init';
|
|
|
14
14
|
import { initialState, reducer } from './reducer';
|
|
15
15
|
|
|
16
16
|
const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
push,
|
|
19
|
+
location: { search },
|
|
20
|
+
} = useHistory();
|
|
18
21
|
const { changeLocale } = useLocalesProvider();
|
|
19
22
|
const { setSkipped } = useGuidedTour();
|
|
20
23
|
const { trackUsage } = useTracking();
|
|
@@ -119,7 +122,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
|
|
119
122
|
auth.setToken(token, body.rememberMe);
|
|
120
123
|
auth.setUserInfo(user, body.rememberMe);
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
redirectToPreviousLocation();
|
|
123
126
|
} catch (err) {
|
|
124
127
|
if (err.response) {
|
|
125
128
|
const errorMessage = get(
|
|
@@ -190,8 +193,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
|
|
190
193
|
return;
|
|
191
194
|
}
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
push('/');
|
|
196
|
+
redirectToPreviousLocation();
|
|
195
197
|
} catch (err) {
|
|
196
198
|
trackUsage('didNotCreateFirstAdmin');
|
|
197
199
|
|
|
@@ -239,6 +241,12 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
|
|
239
241
|
}
|
|
240
242
|
};
|
|
241
243
|
|
|
244
|
+
const redirectToPreviousLocation = () => {
|
|
245
|
+
const locationBeforeAuthenticated = decodeURIComponent(query.get('redirectTo'));
|
|
246
|
+
const redirectUrl = locationBeforeAuthenticated || '/';
|
|
247
|
+
push(redirectUrl);
|
|
248
|
+
};
|
|
249
|
+
|
|
242
250
|
// Redirect the user to the login page if
|
|
243
251
|
// the endpoint does not exist or
|
|
244
252
|
// there is already an admin user oo
|
|
@@ -249,7 +257,16 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
|
|
249
257
|
|
|
250
258
|
// Redirect the user to the register-admin if it is the first user
|
|
251
259
|
if (!hasAdmin && authType !== 'register-admin') {
|
|
252
|
-
return
|
|
260
|
+
return (
|
|
261
|
+
<Redirect
|
|
262
|
+
to={{
|
|
263
|
+
pathname: '/auth/register-admin',
|
|
264
|
+
// Forward the `?redirectTo` from /auth/login
|
|
265
|
+
// /abc => /auth/login?redirectTo=%2Fabc => /auth/register-admin?redirectTo=%2Fabc
|
|
266
|
+
search,
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
253
270
|
}
|
|
254
271
|
|
|
255
272
|
return (
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useQuery } from 'react-query';
|
|
3
2
|
import { useIntl } from 'react-intl';
|
|
4
|
-
import { LoadingIndicatorPage,
|
|
3
|
+
import { LoadingIndicatorPage, useFocusWhenNavigate } from '@strapi/helper-plugin';
|
|
4
|
+
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
5
5
|
import { Layout, HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
|
|
6
6
|
import { Main } from '@strapi/design-system/Main';
|
|
7
|
-
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
8
7
|
import { Typography } from '@strapi/design-system/Typography';
|
|
9
8
|
import { Table, Thead, Tbody, Tr, Td, Th } from '@strapi/design-system/Table';
|
|
10
|
-
import
|
|
9
|
+
import useFetchInstalledPlugins from '../../hooks/useFetchInstalledPlugins';
|
|
11
10
|
|
|
12
11
|
const Plugins = () => {
|
|
13
12
|
const { formatMessage } = useIntl();
|
|
14
|
-
useFocusWhenNavigate();
|
|
15
13
|
const { notifyStatus } = useNotifyAT();
|
|
16
|
-
|
|
14
|
+
useFocusWhenNavigate();
|
|
17
15
|
|
|
18
16
|
const title = formatMessage({
|
|
19
17
|
id: 'app.components.ListPluginsPage.title',
|
|
20
18
|
defaultMessage: 'Plugins',
|
|
21
19
|
});
|
|
22
20
|
|
|
23
|
-
const
|
|
21
|
+
const notifyPluginPageLoad = () => {
|
|
24
22
|
notifyStatus(
|
|
25
23
|
formatMessage(
|
|
26
24
|
{
|
|
@@ -32,14 +30,7 @@ const Plugins = () => {
|
|
|
32
30
|
);
|
|
33
31
|
};
|
|
34
32
|
|
|
35
|
-
const { status, data } =
|
|
36
|
-
onError: () => {
|
|
37
|
-
toggleNotification({
|
|
38
|
-
type: 'warning',
|
|
39
|
-
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
|
40
|
-
});
|
|
41
|
-
},
|
|
42
|
-
});
|
|
33
|
+
const { status, data } = useFetchInstalledPlugins(notifyPluginPageLoad);
|
|
43
34
|
|
|
44
35
|
const isLoading = status !== 'success' && status !== 'error';
|
|
45
36
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { Box } from '@strapi/design-system/Box';
|
|
4
|
+
import { GridLayout } from '@strapi/design-system/Layout';
|
|
5
|
+
|
|
6
|
+
const EmptyPluginCard = styled(Box)`
|
|
7
|
+
background: ${({ theme }) =>
|
|
8
|
+
`linear-gradient(180deg, rgba(234, 234, 239, 0) 0%, ${theme.colors.neutral150} 100%)`};
|
|
9
|
+
opacity: 0.33;
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const EmptyPluginGrid = () => {
|
|
13
|
+
return (
|
|
14
|
+
<GridLayout>
|
|
15
|
+
{Array(12)
|
|
16
|
+
.fill(null)
|
|
17
|
+
.map((_, idx) => (
|
|
18
|
+
<EmptyPluginCard
|
|
19
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
20
|
+
key={`empty-plugin-card-${idx}`}
|
|
21
|
+
height="234px"
|
|
22
|
+
hasRadius
|
|
23
|
+
/>
|
|
24
|
+
))}
|
|
25
|
+
</GridLayout>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
4
|
+
import { Box } from '@strapi/design-system/Box';
|
|
5
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
6
|
+
import { Icon } from '@strapi/design-system/Icon';
|
|
7
|
+
import EmptyStateDocument from '@strapi/icons/EmptyDocuments';
|
|
8
|
+
import { EmptyPluginGrid } from './EmptyPluginGrid';
|
|
9
|
+
|
|
10
|
+
export const EmptyPluginSearch = ({ content }) => {
|
|
11
|
+
return (
|
|
12
|
+
<Box position="relative">
|
|
13
|
+
<EmptyPluginGrid />
|
|
14
|
+
<Box position="absolute" top={11} width="100%">
|
|
15
|
+
<Flex alignItems="center" justifyContent="center" direction="column">
|
|
16
|
+
<Icon as={EmptyStateDocument} color="" width="160px" height="88px" />
|
|
17
|
+
<Box paddingTop={6}>
|
|
18
|
+
<Typography variant="delta" as="p" textColor="neutral600">
|
|
19
|
+
{content}
|
|
20
|
+
</Typography>
|
|
21
|
+
</Box>
|
|
22
|
+
</Flex>
|
|
23
|
+
</Box>
|
|
24
|
+
</Box>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
EmptyPluginSearch.propTypes = {
|
|
29
|
+
content: PropTypes.string.isRequired,
|
|
30
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import styled from 'styled-components';
|
|
5
|
+
import { Box } from '@strapi/design-system/Box';
|
|
6
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
7
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
8
|
+
import { Button } from '@strapi/design-system/Button';
|
|
9
|
+
import { LinkButton } from '@strapi/design-system/LinkButton';
|
|
10
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
11
|
+
import { Icon } from '@strapi/design-system/Icon';
|
|
12
|
+
import { Tooltip } from '@strapi/design-system/Tooltip';
|
|
13
|
+
import ExternalLink from '@strapi/icons/ExternalLink';
|
|
14
|
+
import Duplicate from '@strapi/icons/Duplicate';
|
|
15
|
+
import Check from '@strapi/icons/Check';
|
|
16
|
+
import CheckCircle from '@strapi/icons/CheckCircle';
|
|
17
|
+
import { useNotification, useTracking } from '@strapi/helper-plugin';
|
|
18
|
+
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
19
|
+
import madeByStrapiIcon from '../../../../assets/images/icon_made-by-strapi.svg';
|
|
20
|
+
|
|
21
|
+
// Custom component to have an ellipsis after the 2nd line
|
|
22
|
+
const EllipsisText = styled(Typography)`
|
|
23
|
+
/* stylelint-disable value-no-vendor-prefix, property-no-vendor-prefix */
|
|
24
|
+
display: -webkit-box;
|
|
25
|
+
-webkit-box-orient: vertical;
|
|
26
|
+
-webkit-line-clamp: 2;
|
|
27
|
+
/* stylelint-enable value-no-vendor-prefix, property-no-vendor-prefix */
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const PluginCard = ({ plugin, installedPluginNames, useYarn }) => {
|
|
32
|
+
const { attributes } = plugin;
|
|
33
|
+
const { formatMessage } = useIntl();
|
|
34
|
+
const toggleNotification = useNotification();
|
|
35
|
+
const { trackUsage } = useTracking();
|
|
36
|
+
|
|
37
|
+
const isInstalled = installedPluginNames.includes(attributes.npmPackageName);
|
|
38
|
+
|
|
39
|
+
const commandToCopy = useYarn
|
|
40
|
+
? `yarn add ${attributes.npmPackageName}`
|
|
41
|
+
: `npm install ${attributes.npmPackageName}`;
|
|
42
|
+
|
|
43
|
+
const madeByStrapiMessage = formatMessage({
|
|
44
|
+
id: 'admin.pages.MarketPlacePage.plugin.tooltip.madeByStrapi',
|
|
45
|
+
defaultMessage: 'Made by Strapi',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Flex
|
|
50
|
+
direction="column"
|
|
51
|
+
justifyContent="space-between"
|
|
52
|
+
paddingTop={4}
|
|
53
|
+
paddingRight={6}
|
|
54
|
+
paddingBottom={4}
|
|
55
|
+
paddingLeft={6}
|
|
56
|
+
hasRadius
|
|
57
|
+
background="neutral0"
|
|
58
|
+
shadow="tableShadow"
|
|
59
|
+
height="100%"
|
|
60
|
+
alignItems="normal"
|
|
61
|
+
>
|
|
62
|
+
<Box>
|
|
63
|
+
<Box
|
|
64
|
+
as="img"
|
|
65
|
+
src={attributes.logo.url}
|
|
66
|
+
alt={`${attributes.name} logo`}
|
|
67
|
+
hasRadius
|
|
68
|
+
width={11}
|
|
69
|
+
height={11}
|
|
70
|
+
/>
|
|
71
|
+
<Box paddingTop={4}>
|
|
72
|
+
<Typography as="h3" variant="delta">
|
|
73
|
+
<Flex alignItems="center">
|
|
74
|
+
{attributes.name}
|
|
75
|
+
{attributes.validated && !attributes.madeByStrapi && (
|
|
76
|
+
<Tooltip
|
|
77
|
+
description={formatMessage({
|
|
78
|
+
id: 'admin.pages.MarketPlacePage.plugin.tooltip.verified',
|
|
79
|
+
defaultMessage: 'Plugin verified by Strapi',
|
|
80
|
+
})}
|
|
81
|
+
>
|
|
82
|
+
<Flex>
|
|
83
|
+
<Icon as={CheckCircle} marginLeft={2} color="success600" />
|
|
84
|
+
</Flex>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
)}
|
|
87
|
+
{attributes.madeByStrapi && (
|
|
88
|
+
<Tooltip description={madeByStrapiMessage}>
|
|
89
|
+
<Flex>
|
|
90
|
+
<Box
|
|
91
|
+
as="img"
|
|
92
|
+
src={madeByStrapiIcon}
|
|
93
|
+
alt={madeByStrapiMessage}
|
|
94
|
+
marginLeft={1}
|
|
95
|
+
width={6}
|
|
96
|
+
height="auto"
|
|
97
|
+
/>
|
|
98
|
+
</Flex>
|
|
99
|
+
</Tooltip>
|
|
100
|
+
)}
|
|
101
|
+
</Flex>
|
|
102
|
+
</Typography>
|
|
103
|
+
</Box>
|
|
104
|
+
<Box paddingTop={2}>
|
|
105
|
+
<EllipsisText as="p" variant="omega" textColor="neutral600">
|
|
106
|
+
{attributes.description}
|
|
107
|
+
</EllipsisText>
|
|
108
|
+
</Box>
|
|
109
|
+
</Box>
|
|
110
|
+
|
|
111
|
+
<Stack horizontal spacing={2} style={{ alignSelf: 'flex-end' }} paddingTop={6}>
|
|
112
|
+
<LinkButton
|
|
113
|
+
size="S"
|
|
114
|
+
href={`https://market.strapi.io/plugins/${attributes.slug}`}
|
|
115
|
+
endIcon={<ExternalLink />}
|
|
116
|
+
aria-label={formatMessage(
|
|
117
|
+
{
|
|
118
|
+
id: 'admin.pages.MarketPlacePage.plugin.info.label',
|
|
119
|
+
defaultMessage: 'Learn more about {pluginName}',
|
|
120
|
+
},
|
|
121
|
+
{ pluginName: attributes.name }
|
|
122
|
+
)}
|
|
123
|
+
variant="tertiary"
|
|
124
|
+
onClick={() => trackUsage('didPluginLearnMore')}
|
|
125
|
+
>
|
|
126
|
+
{formatMessage({
|
|
127
|
+
id: 'admin.pages.MarketPlacePage.plugin.info.text',
|
|
128
|
+
defaultMessage: 'Learn more',
|
|
129
|
+
})}
|
|
130
|
+
</LinkButton>
|
|
131
|
+
{isInstalled ? (
|
|
132
|
+
<Box paddingLeft={4}>
|
|
133
|
+
<Icon as={Check} marginRight={2} width={12} height={12} color="success600" />
|
|
134
|
+
<Typography variant="omega" textColor="success600" fontWeight="bold">
|
|
135
|
+
{formatMessage({
|
|
136
|
+
id: 'admin.pages.MarketPlacePage.plugin.installed',
|
|
137
|
+
defaultMessage: 'Installed',
|
|
138
|
+
})}
|
|
139
|
+
</Typography>
|
|
140
|
+
</Box>
|
|
141
|
+
) : (
|
|
142
|
+
<CopyToClipboard
|
|
143
|
+
onCopy={() => {
|
|
144
|
+
trackUsage('willInstallPlugin');
|
|
145
|
+
toggleNotification({
|
|
146
|
+
type: 'success',
|
|
147
|
+
message: { id: 'admin.pages.MarketPlacePage.plugin.copy.success' },
|
|
148
|
+
});
|
|
149
|
+
}}
|
|
150
|
+
text={commandToCopy}
|
|
151
|
+
>
|
|
152
|
+
<Button size="S" startIcon={<Duplicate />} variant="secondary">
|
|
153
|
+
{formatMessage({
|
|
154
|
+
id: 'admin.pages.MarketPlacePage.plugin.copy',
|
|
155
|
+
defaultMessage: 'Copy install command',
|
|
156
|
+
})}
|
|
157
|
+
</Button>
|
|
158
|
+
</CopyToClipboard>
|
|
159
|
+
)}
|
|
160
|
+
</Stack>
|
|
161
|
+
</Flex>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
PluginCard.propTypes = {
|
|
166
|
+
plugin: PropTypes.shape({
|
|
167
|
+
id: PropTypes.string.isRequired,
|
|
168
|
+
attributes: PropTypes.shape({
|
|
169
|
+
name: PropTypes.string.isRequired,
|
|
170
|
+
description: PropTypes.string.isRequired,
|
|
171
|
+
slug: PropTypes.string.isRequired,
|
|
172
|
+
npmPackageName: PropTypes.string.isRequired,
|
|
173
|
+
npmPackageUrl: PropTypes.string.isRequired,
|
|
174
|
+
repositoryUrl: PropTypes.string.isRequired,
|
|
175
|
+
logo: PropTypes.object.isRequired,
|
|
176
|
+
developerName: PropTypes.string.isRequired,
|
|
177
|
+
validated: PropTypes.bool.isRequired,
|
|
178
|
+
madeByStrapi: PropTypes.bool.isRequired,
|
|
179
|
+
strapiCompatibility: PropTypes.oneOf(['v3', 'v4']).isRequired,
|
|
180
|
+
}).isRequired,
|
|
181
|
+
}).isRequired,
|
|
182
|
+
installedPluginNames: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
183
|
+
useYarn: PropTypes.bool.isRequired,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export default PluginCard;
|