mirror of
https://github.com/strapi/strapi.git
synced 2025-06-27 00:41:25 +00:00
feat: make widgets api stable (#23470)
This commit is contained in:
parent
7ae14fd44d
commit
8c28a74d12
@ -1,5 +1,3 @@
|
|||||||
module.exports = ({ env }) => ({
|
module.exports = ({ env }) => ({
|
||||||
future: {
|
future: {},
|
||||||
unstableWidgetsApi: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Box, Flex, Grid, Main, Typography } from '@strapi/design-system';
|
import { Box, Flex, Grid, Main, Typography } from '@strapi/design-system';
|
||||||
import { CheckCircle, Pencil, PuzzlePiece } from '@strapi/icons';
|
import { PuzzlePiece } from '@strapi/icons';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
@ -12,7 +12,6 @@ import { useEnterprise } from '../../ee';
|
|||||||
import { useAuth } from '../../features/Auth';
|
import { useAuth } from '../../features/Auth';
|
||||||
import { useStrapiApp } from '../../features/StrapiApp';
|
import { useStrapiApp } from '../../features/StrapiApp';
|
||||||
|
|
||||||
import { LastEditedWidget, LastPublishedWidget } from './components/ContentManagerWidgets';
|
|
||||||
import { GuidedTour } from './components/GuidedTour';
|
import { GuidedTour } from './components/GuidedTour';
|
||||||
|
|
||||||
import type { WidgetType } from '@strapi/admin/strapi-admin';
|
import type { WidgetType } from '@strapi/admin/strapi-admin';
|
||||||
@ -47,12 +46,7 @@ export const WidgetRoot = ({
|
|||||||
setPermissionStatus(shouldGrant ? 'granted' : 'forbidden');
|
setPermissionStatus(shouldGrant ? 'granted' : 'forbidden');
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (!permissions || permissions.length === 0) {
|
||||||
// TODO: remove unstable check once widgets API is stable
|
|
||||||
!window.strapi.future.isEnabled('unstableWidgetsApi') ||
|
|
||||||
!permissions ||
|
|
||||||
permissions.length === 0
|
|
||||||
) {
|
|
||||||
setPermissionStatus('granted');
|
setPermissionStatus('granted');
|
||||||
} else {
|
} else {
|
||||||
checkPermissions();
|
checkPermissions();
|
||||||
@ -127,7 +121,11 @@ const WidgetComponent = ({ component }: { component: () => Promise<React.Compone
|
|||||||
return <Component />;
|
return <Component />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UnstableHomePageCe = () => {
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* HomePageCE
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const HomePageCE = () => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const user = useAuth('HomePageCE', (state) => state.user);
|
const user = useAuth('HomePageCE', (state) => state.user);
|
||||||
const displayName = user?.firstname ?? user?.username ?? user?.email;
|
const displayName = user?.firstname ?? user?.username ?? user?.email;
|
||||||
@ -174,67 +172,6 @@ const UnstableHomePageCe = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
|
||||||
* HomePageCE
|
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
const HomePageCE = () => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const user = useAuth('HomePageCE', (state) => state.user);
|
|
||||||
const displayName = user?.firstname ?? user?.username ?? user?.email;
|
|
||||||
|
|
||||||
if (window.strapi.future.isEnabled('unstableWidgetsApi')) {
|
|
||||||
return <UnstableHomePageCe />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Main>
|
|
||||||
<Page.Title>
|
|
||||||
{formatMessage({ id: 'HomePage.head.title', defaultMessage: 'Homepage' })}
|
|
||||||
</Page.Title>
|
|
||||||
<Layouts.Header
|
|
||||||
title={formatMessage(
|
|
||||||
{ id: 'HomePage.header.title', defaultMessage: 'Hello {name}' },
|
|
||||||
{ name: displayName }
|
|
||||||
)}
|
|
||||||
subtitle={formatMessage({
|
|
||||||
id: 'HomePage.header.subtitle',
|
|
||||||
defaultMessage: 'Welcome to your administration panel',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<Layouts.Content>
|
|
||||||
<Flex direction="column" alignItems="stretch" gap={8} paddingBottom={10}>
|
|
||||||
<GuidedTour />
|
|
||||||
<Grid.Root gap={5}>
|
|
||||||
<Grid.Item col={6} s={12}>
|
|
||||||
<WidgetRoot
|
|
||||||
title={{
|
|
||||||
id: 'content-manager.widget.last-edited.title',
|
|
||||||
defaultMessage: 'Last edited entries',
|
|
||||||
}}
|
|
||||||
icon={Pencil}
|
|
||||||
>
|
|
||||||
<LastEditedWidget />
|
|
||||||
</WidgetRoot>
|
|
||||||
</Grid.Item>
|
|
||||||
<Grid.Item col={6} s={12}>
|
|
||||||
<WidgetRoot
|
|
||||||
title={{
|
|
||||||
id: 'content-manager.widget.last-published.title',
|
|
||||||
defaultMessage: 'Last published entries',
|
|
||||||
}}
|
|
||||||
icon={CheckCircle}
|
|
||||||
>
|
|
||||||
<LastPublishedWidget />
|
|
||||||
</WidgetRoot>
|
|
||||||
</Grid.Item>
|
|
||||||
</Grid.Root>
|
|
||||||
</Flex>
|
|
||||||
</Layouts.Content>
|
|
||||||
</Main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------------------------------
|
||||||
* HomePage
|
* HomePage
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
import { Box, IconButton, Status, Table, Tbody, Td, Tr, Typography } from '@strapi/design-system';
|
|
||||||
import { Pencil } from '@strapi/icons';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
|
||||||
import { styled } from 'styled-components';
|
|
||||||
|
|
||||||
import { RelativeTime } from '../../../components/RelativeTime';
|
|
||||||
import { Widget } from '../../../components/WidgetHelpers';
|
|
||||||
import { useTracking } from '../../../features/Tracking';
|
|
||||||
import { useGetRecentDocumentsQuery } from '../../../services/homepage';
|
|
||||||
import { capitalise } from '../../../utils/strings';
|
|
||||||
|
|
||||||
import type { RecentDocument } from '../../../../../shared/contracts/homepage';
|
|
||||||
|
|
||||||
const CellTypography = styled(Typography).attrs({ maxWidth: '14.4rem', display: 'block' })`
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface DocumentStatusProps {
|
|
||||||
status: RecentDocument['status'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const DocumentStatus = ({ status = 'draft' }: DocumentStatusProps) => {
|
|
||||||
const statusVariant =
|
|
||||||
status === 'draft' ? 'secondary' : status === 'published' ? 'success' : 'alternative';
|
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Status variant={statusVariant} size="XS">
|
|
||||||
<Typography tag="span" variant="omega" fontWeight="bold">
|
|
||||||
{formatMessage({
|
|
||||||
id: `content-manager.containers.List.${status}`,
|
|
||||||
defaultMessage: capitalise(status),
|
|
||||||
})}
|
|
||||||
</Typography>
|
|
||||||
</Status>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { trackUsage } = useTracking();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const getEditViewLink = (document: RecentDocument): string => {
|
|
||||||
const isSingleType = document.kind === 'singleType';
|
|
||||||
const kindPath = isSingleType ? 'single-types' : 'collection-types';
|
|
||||||
const queryParams = document.locale ? `?plugins[i18n][locale]=${document.locale}` : '';
|
|
||||||
|
|
||||||
return `/content-manager/${kindPath}/${document.contentTypeUid}${isSingleType ? '' : '/' + document.documentId}${queryParams}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowClick = (document: RecentDocument) => () => {
|
|
||||||
trackUsage('willEditEntryFromHome');
|
|
||||||
const link = getEditViewLink(document);
|
|
||||||
navigate(link);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table colCount={5} rowCount={documents?.length ?? 0}>
|
|
||||||
<Tbody>
|
|
||||||
{documents?.map((document) => (
|
|
||||||
<Tr onClick={handleRowClick(document)} cursor="pointer" key={document.documentId}>
|
|
||||||
<Td>
|
|
||||||
<CellTypography title={document.title} variant="omega" textColor="neutral800">
|
|
||||||
{document.title}
|
|
||||||
</CellTypography>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<CellTypography variant="omega" textColor="neutral600">
|
|
||||||
{document.kind === 'singleType'
|
|
||||||
? formatMessage({
|
|
||||||
id: 'content-manager.widget.last-edited.single-type',
|
|
||||||
defaultMessage: 'Single-Type',
|
|
||||||
})
|
|
||||||
: formatMessage({
|
|
||||||
id: document.contentTypeDisplayName,
|
|
||||||
defaultMessage: document.contentTypeDisplayName,
|
|
||||||
})}
|
|
||||||
</CellTypography>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Box display="inline-block">
|
|
||||||
{document.status ? (
|
|
||||||
<DocumentStatus status={document.status} />
|
|
||||||
) : (
|
|
||||||
<Typography textColor="neutral600" aria-hidden>
|
|
||||||
-
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Typography textColor="neutral600">
|
|
||||||
<RelativeTime timestamp={new Date(document.updatedAt)} />
|
|
||||||
</Typography>
|
|
||||||
</Td>
|
|
||||||
<Td onClick={(e) => e.stopPropagation()}>
|
|
||||||
<Box display="inline-block">
|
|
||||||
<IconButton
|
|
||||||
tag={Link}
|
|
||||||
to={getEditViewLink(document)}
|
|
||||||
onClick={() => trackUsage('willEditEntryFromHome')}
|
|
||||||
label={formatMessage({
|
|
||||||
id: 'content-manager.actions.edit.label',
|
|
||||||
defaultMessage: 'Edit',
|
|
||||||
})}
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<Pencil />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
|
||||||
* LastEditedWidget
|
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
const LastEditedWidget = () => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { data, isLoading, error } = useGetRecentDocumentsQuery({ action: 'update' });
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Widget.Loading />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !data) {
|
|
||||||
return <Widget.Error />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Widget.NoData>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'content-manager.widget.last-edited.no-data',
|
|
||||||
defaultMessage: 'No edited entries',
|
|
||||||
})}
|
|
||||||
</Widget.NoData>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <RecentDocumentsTable documents={data} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
|
||||||
* LastPublishedWidget
|
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
const LastPublishedWidget = () => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const { data, isLoading, error } = useGetRecentDocumentsQuery({ action: 'publish' });
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Widget.Loading />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !data) {
|
|
||||||
return <Widget.Error />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
return (
|
|
||||||
<Widget.NoData>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'content-manager.widget.last-published.no-data',
|
|
||||||
defaultMessage: 'No published entries',
|
|
||||||
})}
|
|
||||||
</Widget.NoData>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <RecentDocumentsTable documents={data} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { LastEditedWidget, LastPublishedWidget };
|
|
@ -1,29 +0,0 @@
|
|||||||
import * as Homepage from '../../../shared/contracts/homepage';
|
|
||||||
|
|
||||||
import { adminApi } from './api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Remove this service when the future flag for the widget api is removed
|
|
||||||
*/
|
|
||||||
const homepageService = adminApi
|
|
||||||
.enhanceEndpoints({
|
|
||||||
addTagTypes: ['RecentDocumentList'],
|
|
||||||
})
|
|
||||||
.injectEndpoints({
|
|
||||||
endpoints: (builder) => ({
|
|
||||||
getRecentDocuments: builder.query<
|
|
||||||
Homepage.GetRecentDocuments.Response['data'],
|
|
||||||
Homepage.GetRecentDocuments.Request['query']
|
|
||||||
>({
|
|
||||||
query: (params) => `/content-manager/homepage/recent-documents?action=${params.action}`,
|
|
||||||
transformResponse: (response: Homepage.GetRecentDocuments.Response) => response.data,
|
|
||||||
providesTags: (res, _err, { action }) => [
|
|
||||||
{ type: 'RecentDocumentList' as const, id: action },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { useGetRecentDocumentsQuery } = homepageService;
|
|
||||||
|
|
||||||
export { useGetRecentDocumentsQuery };
|
|
@ -1,7 +1,5 @@
|
|||||||
export interface FeaturesConfig {
|
export interface FeaturesConfig {
|
||||||
future?: {
|
future?: object;
|
||||||
unstableWidgetsApi?: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeaturesService {
|
export interface FeaturesService {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user