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 }) => ({
|
||||
future: {
|
||||
unstableWidgetsApi: true,
|
||||
},
|
||||
future: {},
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
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 { Link as ReactRouterLink } from 'react-router-dom';
|
||||
|
||||
@ -12,7 +12,6 @@ import { useEnterprise } from '../../ee';
|
||||
import { useAuth } from '../../features/Auth';
|
||||
import { useStrapiApp } from '../../features/StrapiApp';
|
||||
|
||||
import { LastEditedWidget, LastPublishedWidget } from './components/ContentManagerWidgets';
|
||||
import { GuidedTour } from './components/GuidedTour';
|
||||
|
||||
import type { WidgetType } from '@strapi/admin/strapi-admin';
|
||||
@ -47,12 +46,7 @@ export const WidgetRoot = ({
|
||||
setPermissionStatus(shouldGrant ? 'granted' : 'forbidden');
|
||||
};
|
||||
|
||||
if (
|
||||
// TODO: remove unstable check once widgets API is stable
|
||||
!window.strapi.future.isEnabled('unstableWidgetsApi') ||
|
||||
!permissions ||
|
||||
permissions.length === 0
|
||||
) {
|
||||
if (!permissions || permissions.length === 0) {
|
||||
setPermissionStatus('granted');
|
||||
} else {
|
||||
checkPermissions();
|
||||
@ -127,7 +121,11 @@ const WidgetComponent = ({ component }: { component: () => Promise<React.Compone
|
||||
return <Component />;
|
||||
};
|
||||
|
||||
const UnstableHomePageCe = () => {
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* HomePageCE
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const HomePageCE = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const user = useAuth('HomePageCE', (state) => state.user);
|
||||
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
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
@ -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 {
|
||||
future?: {
|
||||
unstableWidgetsApi?: boolean;
|
||||
};
|
||||
future?: object;
|
||||
}
|
||||
|
||||
export interface FeaturesService {
|
||||
|
Loading…
x
Reference in New Issue
Block a user