mirror of
https://github.com/strapi/strapi.git
synced 2025-07-04 07:27:23 +00:00
feat(content-releases): Redirect to /content-releases/:releaseId onCreate and add header on details page (#18720)
* draft implementation details header * implementation details ui with mock data * fix unit tests * fix fernando comments * update pages structure * first raw implementation store with rtk * refactor(releases): redux toolkit query work * rename releases page * merge feature/content-releases * test(releases): setup test harness for working with the admin app (#18817) * test(releases): setup test harness for working with the admin app * chore: remove file that shouldn't be here * rename releases page * merge "content-releases/release-details-redirect-after-creation" * test(releases): setup test harness for working with the admin app * rename releases page * merge "content-releases/release-details-redirect-after-creation" --------- Co-authored-by: Simone Taeggi <startae14@gmail.com> * fix Fernando's review comments --------- Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
This commit is contained in:
parent
5ca75a1812
commit
f9fb2e7c49
@ -64,6 +64,7 @@ const getAdminDependencyAliases = (monorepo?: StrapiMonorepo) =>
|
|||||||
*/
|
*/
|
||||||
const devAliases: Record<string, string> = {
|
const devAliases: Record<string, string> = {
|
||||||
'@strapi/admin/strapi-admin': './packages/core/admin/admin/src',
|
'@strapi/admin/strapi-admin': './packages/core/admin/admin/src',
|
||||||
|
'@strapi/content-releases/strapi-admin': './packages/core/content-releases/admin/src',
|
||||||
'@strapi/content-type-builder/strapi-admin': './packages/core/content-type-builder/admin/src',
|
'@strapi/content-type-builder/strapi-admin': './packages/core/content-type-builder/admin/src',
|
||||||
'@strapi/email/strapi-admin': './packages/core/email/admin/src',
|
'@strapi/email/strapi-admin': './packages/core/email/admin/src',
|
||||||
'@strapi/upload/strapi-admin': './packages/core/upload/admin/src',
|
'@strapi/upload/strapi-admin': './packages/core/upload/admin/src',
|
||||||
|
18
packages/core/content-releases/admin/.eslintrc
Normal file
18
packages/core/content-releases/admin/.eslintrc
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["custom/front/typescript"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["./tests/*", "**/*.test.*"],
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
/**
|
||||||
|
* So we can do `import { render } from '@tests/utils'`
|
||||||
|
*/
|
||||||
|
"import/no-unresolved": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ['custom/front/typescript'],
|
|
||||||
};
|
|
@ -7,18 +7,26 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { useNotification } from '@strapi/helper-plugin';
|
import { useAPIErrorHandler, useNotification } from '@strapi/helper-plugin';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
const releaseSchema = yup.object({
|
import { useCreateReleaseMutation } from '../modules/releaseSlice';
|
||||||
|
import { isErrorAxiosError } from '../utils/errors';
|
||||||
|
|
||||||
|
const RELEASE_SCHEMA = yup.object({
|
||||||
name: yup.string().required(),
|
name: yup.string().required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface FormValues {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
const INITIAL_VALUES = {
|
const INITIAL_VALUES = {
|
||||||
name: '',
|
name: '',
|
||||||
};
|
} satisfies FormValues;
|
||||||
|
|
||||||
interface AddReleaseDialogProps {
|
interface AddReleaseDialogProps {
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
@ -27,17 +35,36 @@ interface AddReleaseDialogProps {
|
|||||||
export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
|
const { push } = useHistory();
|
||||||
|
const { formatAPIError } = useAPIErrorHandler();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const [createRelease, { isLoading }] = useCreateReleaseMutation();
|
||||||
handleClose();
|
|
||||||
|
|
||||||
toggleNotification({
|
const handleSubmit = async (values: FormValues) => {
|
||||||
type: 'success',
|
const response = await createRelease({
|
||||||
message: formatMessage({
|
name: values.name,
|
||||||
id: 'content-releases.modal.release-created-notification-success',
|
|
||||||
defaultMessage: 'Release created.',
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ('data' in response) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'success',
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'content-releases.modal.release-created-notification-success',
|
||||||
|
defaultMessage: 'Release created.',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
push(`/plugins/content-releases/${response.data.data.id}`);
|
||||||
|
} else if (isErrorAxiosError(response.error)) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatAPIError(response.error),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -54,7 +81,7 @@ export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
|||||||
validateOnChange={false}
|
validateOnChange={false}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
initialValues={INITIAL_VALUES}
|
initialValues={INITIAL_VALUES}
|
||||||
validationSchema={releaseSchema}
|
validationSchema={RELEASE_SCHEMA}
|
||||||
>
|
>
|
||||||
{({ values, errors, handleChange }) => (
|
{({ values, errors, handleChange }) => (
|
||||||
<Form>
|
<Form>
|
||||||
@ -78,7 +105,7 @@ export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
endActions={
|
endActions={
|
||||||
<Button name="submit" disabled={!values.name} type="submit">
|
<Button name="submit" loading={isLoading} disabled={!values.name} type="submit">
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'content-releases.modal.form.button.submit',
|
id: 'content-releases.modal.form.button.submit',
|
||||||
defaultMessage: 'Continue',
|
defaultMessage: 'Continue',
|
||||||
|
@ -2,6 +2,7 @@ import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|||||||
import { PaperPlane } from '@strapi/icons';
|
import { PaperPlane } from '@strapi/icons';
|
||||||
|
|
||||||
import { PERMISSIONS } from './constants';
|
import { PERMISSIONS } from './constants';
|
||||||
|
import { releaseApi } from './modules/releaseSlice';
|
||||||
import { pluginId } from './pluginId';
|
import { pluginId } from './pluginId';
|
||||||
|
|
||||||
import type { Plugin } from '@strapi/types';
|
import type { Plugin } from '@strapi/types';
|
||||||
@ -19,11 +20,21 @@ const admin: Plugin.Config.AdminInput = {
|
|||||||
defaultMessage: 'Releases',
|
defaultMessage: 'Releases',
|
||||||
},
|
},
|
||||||
async Component() {
|
async Component() {
|
||||||
const { Releases } = await import('./pages/App');
|
const { App } = await import('./pages/App');
|
||||||
return Releases;
|
return App;
|
||||||
},
|
},
|
||||||
permissions: PERMISSIONS.main,
|
permissions: PERMISSIONS.main,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For some reason every middleware you pass has to a function
|
||||||
|
* that returns the actual middleware. It's annoying but no one knows why....
|
||||||
|
*/
|
||||||
|
app.addMiddlewares([() => releaseApi.middleware]);
|
||||||
|
|
||||||
|
app.addReducers({
|
||||||
|
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async registerTrads({ locales }: { locales: string[] }) {
|
async registerTrads({ locales }: { locales: string[] }) {
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||||
|
|
||||||
|
import { pluginId } from '../pluginId';
|
||||||
|
import { axiosBaseQuery } from '../utils/data';
|
||||||
|
|
||||||
|
import type { CreateRelease, GetAllReleases } from '../../../shared/contracts/releases';
|
||||||
|
|
||||||
|
const releaseApi = createApi({
|
||||||
|
reducerPath: pluginId,
|
||||||
|
baseQuery: axiosBaseQuery,
|
||||||
|
tagTypes: ['Releases'],
|
||||||
|
endpoints: (build) => {
|
||||||
|
return {
|
||||||
|
getRelease: build.query<GetAllReleases.Response, undefined>({
|
||||||
|
query() {
|
||||||
|
return {
|
||||||
|
url: '/content-releases',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
providesTags: ['Releases'],
|
||||||
|
}),
|
||||||
|
createRelease: build.mutation<CreateRelease.Response, CreateRelease.Request['body']>({
|
||||||
|
query(data) {
|
||||||
|
return {
|
||||||
|
url: '/content-releases',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
invalidatesTags: ['Releases'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { useGetReleaseQuery, useCreateReleaseMutation } = releaseApi;
|
||||||
|
|
||||||
|
export { useGetReleaseQuery, useCreateReleaseMutation, releaseApi };
|
@ -3,16 +3,18 @@ import { Route, Switch } from 'react-router-dom';
|
|||||||
|
|
||||||
import { pluginId } from '../pluginId';
|
import { pluginId } from '../pluginId';
|
||||||
|
|
||||||
|
import { ProtectedReleaseDetailsPage } from './ReleaseDetailsPage';
|
||||||
import { ProtectedReleasesPage } from './ReleasesPage';
|
import { ProtectedReleasesPage } from './ReleasesPage';
|
||||||
|
|
||||||
export const Releases = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path={`/plugins/${pluginId}`} component={ProtectedReleasesPage} />
|
<Route exact path={`/plugins/${pluginId}`} component={ProtectedReleasesPage} />
|
||||||
<Route
|
<Route
|
||||||
|
exact
|
||||||
path={`/plugins/${pluginId}/:releaseId`}
|
path={`/plugins/${pluginId}/:releaseId`}
|
||||||
render={() => <div>TODO: This is the DetailsPage</div>}
|
component={ProtectedReleaseDetailsPage}
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Main>
|
</Main>
|
||||||
|
@ -0,0 +1,199 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLayout,
|
||||||
|
EmptyStateLayout,
|
||||||
|
Flex,
|
||||||
|
HeaderLayout,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
Popover,
|
||||||
|
Typography,
|
||||||
|
} from '@strapi/design-system';
|
||||||
|
import { CheckPermissions } from '@strapi/helper-plugin';
|
||||||
|
import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { PERMISSIONS } from '../constants';
|
||||||
|
|
||||||
|
const PopoverButton = styled(Flex)`
|
||||||
|
align-self: stretch;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PencilIcon = styled(Pencil)`
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
path {
|
||||||
|
fill: ${({ theme }) => theme.colors.neutral600};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TrashIcon = styled(Trash)`
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
path {
|
||||||
|
fill: ${({ theme }) => theme.colors.danger600};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ReleaseInfoWrapper = styled(Flex)`
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ReleaseDetailsPage = () => {
|
||||||
|
const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
|
||||||
|
const moreButtonRef = React.useRef<HTMLButtonElement>(null!);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
// TODO: get the title from the API
|
||||||
|
const title = 'Release title';
|
||||||
|
|
||||||
|
const totalEntries = 0; // TODO: replace it with the total number of entries
|
||||||
|
const days = 0; // TODO: replace it with the number of days since the release was created
|
||||||
|
const createdBy = 'John Doe'; // TODO: replace it with the name of the user who created the release
|
||||||
|
|
||||||
|
const handleTogglePopover = () => {
|
||||||
|
setIsPopoverVisible((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderLayout
|
||||||
|
title={title}
|
||||||
|
subtitle={formatMessage(
|
||||||
|
{
|
||||||
|
id: 'content-releases.pages.Details.header-subtitle',
|
||||||
|
defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}',
|
||||||
|
},
|
||||||
|
{ number: totalEntries }
|
||||||
|
)}
|
||||||
|
navigationAction={
|
||||||
|
<Link startIcon={<ArrowLeft />} to="/plugins/content-releases">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'global.back',
|
||||||
|
defaultMessage: 'Back',
|
||||||
|
})}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
primaryAction={
|
||||||
|
<Flex gap={2}>
|
||||||
|
<IconButton
|
||||||
|
label={formatMessage({
|
||||||
|
id: 'content-releases.header.actions.open-release-actions',
|
||||||
|
defaultMessage: 'Release actions',
|
||||||
|
})}
|
||||||
|
onClick={handleTogglePopover}
|
||||||
|
ref={moreButtonRef}
|
||||||
|
>
|
||||||
|
<More />
|
||||||
|
</IconButton>
|
||||||
|
{isPopoverVisible && (
|
||||||
|
<Popover
|
||||||
|
source={moreButtonRef}
|
||||||
|
placement="bottom-end"
|
||||||
|
onDismiss={handleTogglePopover}
|
||||||
|
spacing={4}
|
||||||
|
minWidth="242px"
|
||||||
|
>
|
||||||
|
<Flex alignItems="center" justifyContent="center" direction="column" padding={1}>
|
||||||
|
<PopoverButton
|
||||||
|
paddingTop={2}
|
||||||
|
paddingBottom={2}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
alignItems="center"
|
||||||
|
gap={2}
|
||||||
|
as="button"
|
||||||
|
borderRadius="4px"
|
||||||
|
>
|
||||||
|
<PencilIcon />
|
||||||
|
<Typography ellipsis>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.edit',
|
||||||
|
defaultMessage: 'Edit',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</PopoverButton>
|
||||||
|
<PopoverButton
|
||||||
|
paddingTop={2}
|
||||||
|
paddingBottom={2}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
alignItems="center"
|
||||||
|
gap={2}
|
||||||
|
as="button"
|
||||||
|
borderRadius="4px"
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
<Typography ellipsis textColor="danger600">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.delete',
|
||||||
|
defaultMessage: 'Delete',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</PopoverButton>
|
||||||
|
</Flex>
|
||||||
|
<ReleaseInfoWrapper
|
||||||
|
direction="column"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="flex-start"
|
||||||
|
gap={1}
|
||||||
|
padding={5}
|
||||||
|
>
|
||||||
|
<Typography variant="pi" fontWeight="bold">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.created',
|
||||||
|
defaultMessage: 'Created',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="pi" color="neutral300">
|
||||||
|
{formatMessage(
|
||||||
|
{
|
||||||
|
id: 'content-releases.header.actions.created.description',
|
||||||
|
defaultMessage:
|
||||||
|
'{number, plural, =0 {# days} one {# day} other {# days}} ago by {createdBy}',
|
||||||
|
},
|
||||||
|
{ number: days, createdBy }
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</ReleaseInfoWrapper>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
<Button size="S" variant="tertiary">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.refresh',
|
||||||
|
defaultMessage: 'Refresh',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
<Button size="S" disabled={true} variant="default">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.release',
|
||||||
|
defaultMessage: 'Release',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ContentLayout>
|
||||||
|
<EmptyStateLayout
|
||||||
|
content={formatMessage({
|
||||||
|
id: 'content-releases.pages.Details.empty-state.content',
|
||||||
|
defaultMessage: 'This release is empty.',
|
||||||
|
})}
|
||||||
|
icon={<EmptyDocuments width="10rem" />}
|
||||||
|
/>
|
||||||
|
</ContentLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProtectedReleaseDetailsPage = () => (
|
||||||
|
<CheckPermissions permissions={PERMISSIONS.main}>
|
||||||
|
<ReleaseDetailsPage />
|
||||||
|
</CheckPermissions>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { ReleaseDetailsPage, ProtectedReleaseDetailsPage };
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Button, HeaderLayout } from '@strapi/design-system';
|
import { Button, HeaderLayout } from '@strapi/design-system';
|
||||||
import { CheckPermissions, CheckPagePermissions } from '@strapi/helper-plugin';
|
import { CheckPermissions } from '@strapi/helper-plugin';
|
||||||
import { Plus } from '@strapi/icons';
|
import { Plus } from '@strapi/icons';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { render, screen } from '@tests/utils';
|
||||||
|
|
||||||
|
import { ReleaseDetailsPage } from '../ReleaseDetailsPage';
|
||||||
|
|
||||||
|
describe('Release details page', () => {
|
||||||
|
it('renders correctly the heading content', async () => {
|
||||||
|
const { user } = render(<ReleaseDetailsPage />);
|
||||||
|
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Release title');
|
||||||
|
// if there are 0 entries
|
||||||
|
expect(screen.getByText('No entries')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const refreshButton = screen.getByRole('button', { name: 'Refresh' });
|
||||||
|
expect(refreshButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
const releaseButton = screen.getByRole('button', { name: 'Release' });
|
||||||
|
expect(releaseButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
const moreButton = screen.getByRole('button', { name: 'Release actions' });
|
||||||
|
expect(moreButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
await user.click(moreButton);
|
||||||
|
|
||||||
|
// shows the popover actions
|
||||||
|
const editButton = screen.getByRole('button', { name: 'Edit' });
|
||||||
|
expect(editButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
const deleteButton = screen.getByRole('button', { name: 'Delete' });
|
||||||
|
expect(deleteButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows empty content if there are no entries', async () => {
|
||||||
|
render(<ReleaseDetailsPage />);
|
||||||
|
|
||||||
|
expect(screen.getByText('No entries')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -1,36 +1,18 @@
|
|||||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
import { within, screen } from '@testing-library/react';
|
||||||
import { render as renderRTL, within, screen } from '@testing-library/react';
|
import { render } from '@tests/utils';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
|
|
||||||
import { ReleasesPage } from '../ReleasesPage';
|
import { ReleasesPage } from '../ReleasesPage';
|
||||||
|
|
||||||
const user = userEvent.setup();
|
|
||||||
|
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
...jest.requireActual('@strapi/helper-plugin'),
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
CheckPermissions: ({ children }: { children: JSX.Element}) => <div>{children}</div>
|
CheckPermissions: ({ children }: { children: JSX.Element}) => <div>{children}</div>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = () =>
|
|
||||||
renderRTL(
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
||||||
<ReleasesPage />
|
|
||||||
</IntlProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('Releases home page', () => {
|
describe('Releases home page', () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders correctly the heading content', async () => {
|
it('renders correctly the heading content', async () => {
|
||||||
render();
|
const { user } = render(<ReleasesPage />);
|
||||||
|
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Releases');
|
||||||
() => expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Releases');
|
|
||||||
// if there are 0 releases
|
// if there are 0 releases
|
||||||
expect(screen.getByText('No releases')).toBeInTheDocument();
|
expect(screen.getByText('No releases')).toBeInTheDocument();
|
||||||
|
|
||||||
@ -51,7 +33,7 @@ describe('Releases home page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('hides the dialog', async () => {
|
it('hides the dialog', async () => {
|
||||||
render();
|
const { user } = render(<ReleasesPage />);
|
||||||
const newReleaseButton = screen.getByRole('button', { name: 'New release' });
|
const newReleaseButton = screen.getByRole('button', { name: 'New release' });
|
||||||
await user.click(newReleaseButton);
|
await user.click(newReleaseButton);
|
||||||
|
|
||||||
@ -65,7 +47,7 @@ describe('Releases home page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('enables the submit button when there is content in the input', async () => {
|
it('enables the submit button when there is content in the input', async () => {
|
||||||
render();
|
const { user } = render(<ReleasesPage />);
|
||||||
const newReleaseButton = screen.getByRole('button', { name: 'New release' });
|
const newReleaseButton = screen.getByRole('button', { name: 'New release' });
|
||||||
await user.click(newReleaseButton);
|
await user.click(newReleaseButton);
|
||||||
|
|
||||||
|
@ -3,8 +3,16 @@
|
|||||||
"pages.Releases.title": "Releases",
|
"pages.Releases.title": "Releases",
|
||||||
"pages.Releases.header-subtitle": "{number, plural, =0 {No releases} one {# release} other {# releases}}",
|
"pages.Releases.header-subtitle": "{number, plural, =0 {No releases} one {# release} other {# releases}}",
|
||||||
"header.actions.add-release": "New Release",
|
"header.actions.add-release": "New Release",
|
||||||
|
"header.actions.refresh": "Refresh",
|
||||||
|
"header.actions.release": "Release",
|
||||||
|
"header.actions.open-release-actions": "Release actions",
|
||||||
|
"header.actions.edit": "Edit",
|
||||||
|
"header.actions.delete": "Delete",
|
||||||
|
"header.actions.created": "Created",
|
||||||
|
"header.actions.created.description": "{number, plural, =0 {# days} one {# day} other {# days}} ago by {user}",
|
||||||
"modal.release-created-notification-success": "Release created",
|
"modal.release-created-notification-success": "Release created",
|
||||||
"modal.add-release-title": "New Release",
|
"modal.add-release-title": "New Release",
|
||||||
"modal.form.input.label.release-name": "Name",
|
"modal.form.input.label.release-name": "Name",
|
||||||
"modal.form.button.submit": "Continue"
|
"modal.form.button.submit": "Continue",
|
||||||
|
"pages.Details.header-subtitle": "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
|
||||||
}
|
}
|
||||||
|
61
packages/core/content-releases/admin/src/utils/data.ts
Normal file
61
packages/core/content-releases/admin/src/utils/data.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { getFetchClient } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export interface QueryArguments<TSend> {
|
||||||
|
url: string;
|
||||||
|
method: 'PUT' | 'GET' | 'POST' | 'DELETE';
|
||||||
|
data?: TSend;
|
||||||
|
config?: AxiosRequestConfig<TSend>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosBaseQuery = async <TData = any, TSend = any>({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
}: QueryArguments<TSend>) => {
|
||||||
|
try {
|
||||||
|
const { get, post, del, put } = getFetchClient();
|
||||||
|
|
||||||
|
if (method === 'POST') {
|
||||||
|
const res = await post<TData, AxiosResponse<TData>, TSend>(url, data, config);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (method === 'DELETE') {
|
||||||
|
const res = await del<TData, AxiosResponse<TData>, TSend>(url, config);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (method === 'PUT') {
|
||||||
|
const res = await put<TData, AxiosResponse<TData>, TSend>(url, data, config);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default is GET.
|
||||||
|
*/
|
||||||
|
const res = await get<TData, AxiosResponse<TData>, TSend>(url, config);
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as AxiosError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This format mimics what we want from an AxiosError which is what the
|
||||||
|
* rest of the app works with, except this format is "serializable" since
|
||||||
|
* it goes into the redux store.
|
||||||
|
*
|
||||||
|
* NOTE – passing the whole response will highlight this "serializability" issue.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
error: {
|
||||||
|
status: err.response?.status,
|
||||||
|
code: err.code,
|
||||||
|
response: {
|
||||||
|
data: err.response?.data,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { axiosBaseQuery };
|
19
packages/core/content-releases/admin/src/utils/errors.ts
Normal file
19
packages/core/content-releases/admin/src/utils/errors.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asserts the errors from redux-toolkit-query are
|
||||||
|
* axios errors so we can pass them to our utility functions
|
||||||
|
* to correctly render error messages.
|
||||||
|
*/
|
||||||
|
const isErrorAxiosError = (err: unknown): err is AxiosError<{ error: any }> => {
|
||||||
|
return (
|
||||||
|
typeof err === 'object' &&
|
||||||
|
err !== null &&
|
||||||
|
'response' in err &&
|
||||||
|
typeof err.response === 'object' &&
|
||||||
|
err.response !== null &&
|
||||||
|
'data' in err.response
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { isErrorAxiosError };
|
4
packages/core/content-releases/admin/tests/server.ts
Normal file
4
packages/core/content-releases/admin/tests/server.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// import { rest } from 'msw';
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
|
||||||
|
export const server = setupServer(...[]);
|
13
packages/core/content-releases/admin/tests/setup.ts
Normal file
13
packages/core/content-releases/admin/tests/setup.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { server } from './server';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
server.resetHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
23
packages/core/content-releases/admin/tests/store.ts
Normal file
23
packages/core/content-releases/admin/tests/store.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { fixtures } from '@strapi/admin-test-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for the redux store in `utils`.
|
||||||
|
* The more we adopt it, the bigger it will get – which is okay.
|
||||||
|
*/
|
||||||
|
const initialState = {
|
||||||
|
admin_app: { permissions: fixtures.permissions.app },
|
||||||
|
rbacProvider: {
|
||||||
|
allPermissions: [
|
||||||
|
...fixtures.permissions.allPermissions,
|
||||||
|
{
|
||||||
|
id: 314,
|
||||||
|
action: 'admin::users.read',
|
||||||
|
subject: null,
|
||||||
|
properties: {},
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { initialState };
|
117
packages/core/content-releases/admin/tests/utils.tsx
Normal file
117
packages/core/content-releases/admin/tests/utils.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable check-file/filename-naming-convention */
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import { fixtures } from '@strapi/admin-test-utils';
|
||||||
|
import { DesignSystemProvider } from '@strapi/design-system';
|
||||||
|
import { NotificationsProvider, Permission, RBACContext } from '@strapi/helper-plugin';
|
||||||
|
import {
|
||||||
|
renderHook as renderHookRTL,
|
||||||
|
render as renderRTL,
|
||||||
|
waitFor,
|
||||||
|
RenderOptions as RTLRenderOptions,
|
||||||
|
RenderResult,
|
||||||
|
act,
|
||||||
|
screen,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { MemoryRouter, MemoryRouterProps } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { releaseApi } from '../src/modules/releaseSlice';
|
||||||
|
|
||||||
|
import { server } from './server';
|
||||||
|
import { initialState } from './store';
|
||||||
|
|
||||||
|
interface ProvidersProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||||
|
const store = configureStore({
|
||||||
|
preloadedState: initialState,
|
||||||
|
reducer: {
|
||||||
|
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||||
|
admin_app: (state = initialState) => state,
|
||||||
|
rbacProvider: (state = initialState) => state,
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(releaseApi.middleware),
|
||||||
|
});
|
||||||
|
|
||||||
|
// en is the default locale of the admin app.
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<MemoryRouter initialEntries={initialEntries}>
|
||||||
|
<DesignSystemProvider locale="en">
|
||||||
|
<IntlProvider locale="en" messages={{}} textComponent="span">
|
||||||
|
<NotificationsProvider>
|
||||||
|
<RBACContext.Provider
|
||||||
|
value={{
|
||||||
|
refetchPermissions: jest.fn(),
|
||||||
|
allPermissions: [
|
||||||
|
...fixtures.permissions.allPermissions,
|
||||||
|
{
|
||||||
|
id: 314,
|
||||||
|
action: 'admin::users.read',
|
||||||
|
subject: null,
|
||||||
|
properties: {},
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: {},
|
||||||
|
},
|
||||||
|
] as Permission[],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RBACContext.Provider>
|
||||||
|
</NotificationsProvider>
|
||||||
|
</IntlProvider>
|
||||||
|
</DesignSystemProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
const fallbackWrapper = ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||||
|
|
||||||
|
export interface RenderOptions {
|
||||||
|
renderOptions?: RTLRenderOptions;
|
||||||
|
userEventOptions?: Parameters<typeof userEvent.setup>[0];
|
||||||
|
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = (
|
||||||
|
ui: React.ReactElement,
|
||||||
|
{ renderOptions, userEventOptions, initialEntries }: RenderOptions = {}
|
||||||
|
): RenderResult & { user: ReturnType<typeof userEvent.setup> } => {
|
||||||
|
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = renderOptions ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...renderRTL(ui, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Providers initialEntries={initialEntries}>
|
||||||
|
<Wrapper>{children}</Wrapper>
|
||||||
|
</Providers>
|
||||||
|
),
|
||||||
|
...restOptions,
|
||||||
|
}),
|
||||||
|
user: userEvent.setup(userEventOptions),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHook: typeof renderHookRTL = (hook, options) => {
|
||||||
|
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = options ?? {};
|
||||||
|
|
||||||
|
return renderHookRTL(hook, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Providers>
|
||||||
|
<Wrapper>{children}</Wrapper>
|
||||||
|
</Providers>
|
||||||
|
),
|
||||||
|
...restOptions,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { render, renderHook, waitFor, act, screen, server };
|
9
packages/core/content-releases/admin/tsconfig.build.json
Normal file
9
packages/core/content-releases/admin/tsconfig.build.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "./admin/tsconfig.json",
|
||||||
|
"include": ["./admin/src", "./admin/custom.d.ts", "./shared"],
|
||||||
|
"exclude": ["tests", "**/*.test.*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": ".",
|
||||||
|
"outDir": "./dist"
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "tsconfig/client.json",
|
"extends": "tsconfig/client.json",
|
||||||
"include": [
|
|
||||||
"src",
|
|
||||||
"custom.d.ts"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
"rootDir": "../",
|
"rootDir": "../",
|
||||||
}
|
"paths": {
|
||||||
|
"@tests/*": ["./tests/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "../shared", "tests", "custom.d.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
preset: '../../../jest-preset.front.js',
|
preset: '../../../jest-preset.front.js',
|
||||||
displayName: 'Core Content Releases',
|
displayName: 'Core Content Releases',
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@tests/(.*)$': '<rootDir>/admin/tests/$1',
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: ['./admin/tests/setup.ts'],
|
||||||
};
|
};
|
||||||
|
@ -40,21 +40,14 @@
|
|||||||
"./dist",
|
"./dist",
|
||||||
"strapi-server.js"
|
"strapi-server.js"
|
||||||
],
|
],
|
||||||
"strapi": {
|
|
||||||
"name": "content-releases",
|
|
||||||
"description": "Organize and release content",
|
|
||||||
"kind": "plugin",
|
|
||||||
"displayName": "Releases",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pack-up build",
|
"build": "pack-up build",
|
||||||
"clean": "run -T rimraf ./dist",
|
"clean": "run -T rimraf ./dist",
|
||||||
"lint": "run -T eslint .",
|
"lint": "run -T eslint .",
|
||||||
"prepublishOnly": "yarn clean && yarn build",
|
"prepublishOnly": "yarn clean && yarn build",
|
||||||
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
|
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
|
||||||
"test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
|
||||||
"test:front:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js",
|
"test:front:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js",
|
||||||
|
"test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||||
"test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
"test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
||||||
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
||||||
"test:unit": "run -T jest",
|
"test:unit": "run -T jest",
|
||||||
@ -68,11 +61,14 @@
|
|||||||
"@strapi/icons": "1.13.0",
|
"@strapi/icons": "1.13.0",
|
||||||
"@strapi/types": "workspace:*",
|
"@strapi/types": "workspace:*",
|
||||||
"@strapi/utils": "4.15.4",
|
"@strapi/utils": "4.15.4",
|
||||||
|
"axios": "1.6.0",
|
||||||
"formik": "2.4.0",
|
"formik": "2.4.0",
|
||||||
"react-intl": "6.4.1",
|
"react-intl": "6.4.1",
|
||||||
|
"react-redux": "8.1.1",
|
||||||
"yup": "0.32.9"
|
"yup": "0.32.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@strapi/admin-test-utils": "4.15.4",
|
||||||
"@strapi/pack-up": "workspace:*",
|
"@strapi/pack-up": "workspace:*",
|
||||||
"@strapi/strapi": "4.15.4",
|
"@strapi/strapi": "4.15.4",
|
||||||
"@testing-library/react": "14.0.0",
|
"@testing-library/react": "14.0.0",
|
||||||
@ -80,6 +76,7 @@
|
|||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/styled-components": "5.1.26",
|
"@types/styled-components": "5.1.26",
|
||||||
"koa": "2.13.4",
|
"koa": "2.13.4",
|
||||||
|
"msw": "1.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "5.3.4",
|
"react-router-dom": "5.3.4",
|
||||||
@ -101,5 +98,12 @@
|
|||||||
"implicitDependencies": [
|
"implicitDependencies": [
|
||||||
"!@strapi/strapi"
|
"!@strapi/strapi"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"strapi": {
|
||||||
|
"name": "content-releases",
|
||||||
|
"description": "Organize and release content",
|
||||||
|
"kind": "plugin",
|
||||||
|
"displayName": "Releases",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ export default defineConfig({
|
|||||||
import: './dist/admin/index.mjs',
|
import: './dist/admin/index.mjs',
|
||||||
require: './dist/admin/index.js',
|
require: './dist/admin/index.js',
|
||||||
types: './dist/admin/src/index.d.ts',
|
types: './dist/admin/src/index.d.ts',
|
||||||
|
tsconfig: './admin/tsconfig.build.json',
|
||||||
runtime: 'web',
|
runtime: 'web',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -14,6 +15,7 @@ export default defineConfig({
|
|||||||
import: './dist/server/index.mjs',
|
import: './dist/server/index.mjs',
|
||||||
require: './dist/server/index.js',
|
require: './dist/server/index.js',
|
||||||
types: './dist/server/src/index.d.ts',
|
types: './dist/server/src/index.d.ts',
|
||||||
|
tsconfig: './server/tsconfig.build.json',
|
||||||
runtime: 'node',
|
runtime: 'node',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
48
packages/core/content-releases/shared/contracts/releases.ts
Normal file
48
packages/core/content-releases/shared/contracts/releases.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Entity as StrapiEntity } from '@strapi/types';
|
||||||
|
import { errors } from '@strapi/utils';
|
||||||
|
|
||||||
|
export interface Entity {
|
||||||
|
id: StrapiEntity.ID;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Release extends Entity {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /content-releases - Create a single release
|
||||||
|
*/
|
||||||
|
export declare namespace CreateRelease {
|
||||||
|
export interface Request {
|
||||||
|
query: {};
|
||||||
|
body: Omit<Release, keyof Entity>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
data: Release;
|
||||||
|
/**
|
||||||
|
* TODO: check if we also could recieve errors.YupValidationError
|
||||||
|
*/
|
||||||
|
error?: errors.ApplicationError | errors.YupValidationError | errors.UnauthorizedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /content-releases - Get all the release
|
||||||
|
*/
|
||||||
|
export declare namespace GetAllReleases {
|
||||||
|
export interface Request {
|
||||||
|
query: {};
|
||||||
|
body: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Validate this with BE
|
||||||
|
*/
|
||||||
|
export interface Response {
|
||||||
|
data: Release[];
|
||||||
|
error?: errors.ApplicationError;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "tsconfig/client.json",
|
|
||||||
"include": [
|
|
||||||
"./admin",
|
|
||||||
"./server"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"declarationDir": "./dist",
|
|
||||||
"outDir": "./dist"
|
|
||||||
}
|
|
||||||
}
|
|
@ -8764,6 +8764,7 @@ __metadata:
|
|||||||
resolution: "@strapi/content-releases@workspace:packages/core/content-releases"
|
resolution: "@strapi/content-releases@workspace:packages/core/content-releases"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@reduxjs/toolkit": "npm:1.9.7"
|
"@reduxjs/toolkit": "npm:1.9.7"
|
||||||
|
"@strapi/admin-test-utils": "npm:4.15.4"
|
||||||
"@strapi/design-system": "npm:1.13.1"
|
"@strapi/design-system": "npm:1.13.1"
|
||||||
"@strapi/helper-plugin": "npm:4.15.4"
|
"@strapi/helper-plugin": "npm:4.15.4"
|
||||||
"@strapi/icons": "npm:1.13.0"
|
"@strapi/icons": "npm:1.13.0"
|
||||||
@ -8775,11 +8776,14 @@ __metadata:
|
|||||||
"@testing-library/user-event": "npm:14.4.3"
|
"@testing-library/user-event": "npm:14.4.3"
|
||||||
"@types/koa": "npm:2.13.4"
|
"@types/koa": "npm:2.13.4"
|
||||||
"@types/styled-components": "npm:5.1.26"
|
"@types/styled-components": "npm:5.1.26"
|
||||||
|
axios: "npm:1.6.0"
|
||||||
formik: "npm:2.4.0"
|
formik: "npm:2.4.0"
|
||||||
koa: "npm:2.13.4"
|
koa: "npm:2.13.4"
|
||||||
|
msw: "npm:1.3.0"
|
||||||
react: "npm:^18.2.0"
|
react: "npm:^18.2.0"
|
||||||
react-dom: "npm:^18.2.0"
|
react-dom: "npm:^18.2.0"
|
||||||
react-intl: "npm:6.4.1"
|
react-intl: "npm:6.4.1"
|
||||||
|
react-redux: "npm:8.1.1"
|
||||||
react-router-dom: "npm:5.3.4"
|
react-router-dom: "npm:5.3.4"
|
||||||
styled-components: "npm:5.3.3"
|
styled-components: "npm:5.3.3"
|
||||||
typescript: "npm:5.2.2"
|
typescript: "npm:5.2.2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user