diff --git a/.changeset/smart-weeks-behave.md b/.changeset/smart-weeks-behave.md new file mode 100644 index 0000000000..c341c49c13 --- /dev/null +++ b/.changeset/smart-weeks-behave.md @@ -0,0 +1,23 @@ +--- +"@comet/admin": minor +--- + +Add `Alert` component + +**Example:** + +```tsx +import { Alert, OkayButton, SaveButton } from "@comet/admin"; + +}> + Action Text + + } +> + Notification Text + +``` \ No newline at end of file diff --git a/.changeset/tricky-adults-laugh.md b/.changeset/tricky-adults-laugh.md new file mode 100644 index 0000000000..4c34ff0d0e --- /dev/null +++ b/.changeset/tricky-adults-laugh.md @@ -0,0 +1,5 @@ +--- +"@comet/cms-admin": minor +--- + +Make all DAM license fields optional if `LicenseType` is `ROYALTY_FREE` even if `requireLicense` is true in `DamConfig` diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000000..b0ea7f3183 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,6 @@ +addAssignees: author + +addReviewers: true + +reviewers: + - johnnyomair diff --git a/demo/api/src/db/fixtures/fixtures.console.ts b/demo/api/src/db/fixtures/fixtures.console.ts index 667c0f56e9..23d4691f92 100644 --- a/demo/api/src/db/fixtures/fixtures.console.ts +++ b/demo/api/src/db/fixtures/fixtures.console.ts @@ -19,6 +19,7 @@ import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category"; import { PageContentBlock } from "@src/pages/blocks/PageContentBlock"; import { PageInput } from "@src/pages/dto/page.input"; import { Page } from "@src/pages/entities/page.entity"; +import { UserGroup } from "@src/user-groups/user-group"; import faker from "faker"; import { Command, Console } from "nestjs-console"; import slugify from "slugify"; @@ -104,6 +105,8 @@ export class FixturesConsole { id: attachedDocumentIds[0], type: "Page", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, @@ -113,7 +116,14 @@ export class FixturesConsole { await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); node = await this.pageTreeService.createNode( - { name: "Sub", slug: "sub", parentId: node.id, attachedDocument: { id: attachedDocumentIds[1], type: "Page" } }, + { + name: "Sub", + slug: "sub", + parentId: node.id, + attachedDocument: { id: attachedDocumentIds[1], type: "Page" }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, + }, PageTreeNodeCategory.MainNavigation, scope, ); @@ -129,6 +139,8 @@ export class FixturesConsole { id: attachedDocumentIds[2], type: "Page", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, @@ -144,6 +156,8 @@ export class FixturesConsole { attachedDocument: { type: "Page", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, @@ -160,6 +174,8 @@ export class FixturesConsole { id: attachedDocumentIds[3], type: "Link", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, @@ -176,6 +192,8 @@ export class FixturesConsole { id: attachedDocumentIds[4], type: "Page", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, @@ -226,6 +244,8 @@ export class FixturesConsole { slug: slugify(name), parentId: level > 0 ? faker.random.arrayElement(pages[level - 1]).id : undefined, attachedDocument: { type: "Page" }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, { diff --git a/demo/api/src/db/fixtures/generators/many-images-test-page.generator.ts b/demo/api/src/db/fixtures/generators/many-images-test-page.generator.ts index dd5077d3cc..6cdc3f3fb0 100644 --- a/demo/api/src/db/fixtures/generators/many-images-test-page.generator.ts +++ b/demo/api/src/db/fixtures/generators/many-images-test-page.generator.ts @@ -49,6 +49,8 @@ export class ManyImagesTestPageGenerator { id: uuidDocument, type: "Page", }, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, }, PageTreeNodeCategory.MainNavigation, scope, diff --git a/packages/admin/admin-stories/src/admin/alert/Alert.tsx b/packages/admin/admin-stories/src/admin/alert/Alert.tsx new file mode 100644 index 0000000000..fc46057202 --- /dev/null +++ b/packages/admin/admin-stories/src/admin/alert/Alert.tsx @@ -0,0 +1,152 @@ +import { Alert, OkayButton, SaveButton } from "@comet/admin"; +import { ArrowRight } from "@comet/admin-icons"; +import { Button, Card, CardContent, Typography } from "@mui/material"; +import { Stack } from "@mui/system"; +import { storiesOf } from "@storybook/react"; +import React from "react"; + +function Story() { + return ( +
+ + + + With Title + + + }> + Action Text + + } + onClose={() => { + // noop + }} + > + Notification Text + + }> + Action Text + + } + onClose={() => { + // noop + }} + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel vehicula est. Nunc congue velit sem, ac porttitor + massa semper nec. Proin quis volutpat magna. Mauris eget libero et mi imperdiet ultrices. Donec eget interdum odio. + Maecenas blandit ipsum et eros tempus porttitor. Aliquam erat volutpat. + + }> + Action Text + + } + onClose={() => { + // noop + }} + > + Notification Text + + { + // noop + }} + > + Notification Text + + + + + + + + + Without Title + + + { + // noop + }} + > + Notification Text + + { + // noop + }} + > + Notification Text + + { + // noop + }} + > + Notification Text + + }> + Action Text + + } + onClose={() => { + // noop + }} + > + Notification Text + + + + + + + + + Without Close Button + + Notification Text + + + Notification Text + + }> + Action Text + + } + > + Notification Text + + Other Actions + }> + Text + + } /> + + + +
+ ); +} + +storiesOf("@comet/admin/alert/Alert", module).add("Alerts", () => ); diff --git a/packages/admin/admin-theme/src/componentsTheme/MuiAlert.tsx b/packages/admin/admin-theme/src/componentsTheme/MuiAlert.tsx new file mode 100644 index 0000000000..17710c670b --- /dev/null +++ b/packages/admin/admin-theme/src/componentsTheme/MuiAlert.tsx @@ -0,0 +1,45 @@ +import { Check, Error, Info, Warning } from "@comet/admin-icons"; +import React from "react"; + +import { mergeOverrideStyles } from "../utils/mergeOverrideStyles"; +import { GetMuiComponentTheme } from "./getComponentsTheme"; + +export const getMuiAlert: GetMuiComponentTheme<"MuiAlert"> = (component, { palette }) => ({ + ...component, + defaultProps: { + variant: "outlined", + + iconMapping: { + info: , + success: , + error: , + warning: , + }, + ...component?.defaultProps, + }, + styleOverrides: mergeOverrideStyles<"MuiAlert">(component?.styleOverrides, { + root: {}, + outlined: { + borderLeftWidth: 5, + backgroundColor: "#fff", + borderRadius: 4, + color: palette.grey[800], + }, + outlinedSuccess: { + borderColor: "#14CC33", + }, + outlinedInfo: { + borderColor: "#29B6F6", + }, + outlinedWarning: { + borderColor: "#FFB31A", + }, + outlinedError: { + borderColor: "#D11700", + }, + icon: { + marginRight: 0, + padding: 0, + }, + }), +}); diff --git a/packages/admin/admin-theme/src/componentsTheme/getComponentsTheme.ts b/packages/admin/admin-theme/src/componentsTheme/getComponentsTheme.ts index cbb9771a1a..d69840a9b2 100644 --- a/packages/admin/admin-theme/src/componentsTheme/getComponentsTheme.ts +++ b/packages/admin/admin-theme/src/componentsTheme/getComponentsTheme.ts @@ -6,6 +6,7 @@ import { ZIndex } from "@mui/material/styles/zIndex"; import { Spacing } from "@mui/system"; import { getMuiAccordion } from "./MuiAccordion"; +import { getMuiAlert } from "./MuiAlert"; import { getMuiAppBar } from "./MuiAppBar"; import { getMuiAutocomplete } from "./MuiAutocomplete"; import { getMuiButton } from "./MuiButton"; @@ -101,4 +102,5 @@ export const getComponentsTheme = (components: Components, themeData: ThemeData) MuiToggleButtonGroup: getMuiToggleButtonGroup(components.MuiToggleButtonGroup, themeData), MuiTooltip: getMuiTooltip(components.MuiTooltip, themeData), MuiTypography: getMuiTypography(components.MuiTypography, themeData), + MuiAlert: getMuiAlert(components.MuiAlert, themeData), }); diff --git a/packages/admin/admin/src/alert/Alert.tsx b/packages/admin/admin/src/alert/Alert.tsx new file mode 100644 index 0000000000..ec9fff05de --- /dev/null +++ b/packages/admin/admin/src/alert/Alert.tsx @@ -0,0 +1,116 @@ +import { Close } from "@comet/admin-icons"; +import { Alert as MuiAlert, AlertTitle, buttonClasses, IconButton, Theme, Typography } from "@mui/material"; +import { createStyles, WithStyles, withStyles } from "@mui/styles"; +import clsx from "clsx"; +import * as React from "react"; + +export interface AlertProps { + severity?: "info" | "warning" | "error" | "success"; + title?: React.ReactNode; + children?: React.ReactNode; + onClose?: () => void; + action?: React.ReactNode; +} + +export type AlertClassKey = "root" | "message" | "title" | "text" | "action" | "closeIcon" | "hasTitle"; + +const styles = (theme: Theme) => + createStyles({ + root: { + display: "flex", + alignItems: "center", + backgroundColor: theme.palette.background.paper, + borderRadius: 4, + boxShadow: theme.shadows[2], + position: "relative", + padding: theme.spacing(2, "12px", 2, 4), + minHeight: 40, // to ensure consistent height for the content, regardless of the presence of a button or close icon, in order to set the outer padding correctly + }, + message: { + display: "flex", + alignItems: "center", + flexGrow: 1, + padding: 0, + paddingLeft: theme.spacing(2), + marginBottom: 0, + }, + title: { + fontWeight: 600, + marginBottom: theme.spacing(1), + }, + text: { + flexGrow: 1, + marginRight: theme.spacing(4), + }, + action: {}, + closeIcon: {}, + hasTitle: { + alignItems: "flex-start", + + [`& .${buttonClasses.text}`]: { + marginLeft: -15, + }, + + "& $action": { + marginTop: theme.spacing(2), + }, + + "& $closeIcon": { + position: "absolute", + right: 10, + top: 10, + }, + "& $message": { + flexDirection: "column", + alignItems: "flex-start", + }, + "&$root": { + paddingBottom: "6px", + paddingTop: theme.spacing(4), + }, + }, + }); + +function Alert({ severity = "info", title, children, classes, onClose, action }: AlertProps & WithStyles): React.ReactElement { + return ( + + {Boolean(title) && {title}} + + {children} + +
{action}
+ {onClose && ( + + + + )} +
+ ); +} + +const AdminComponentWithStyles = withStyles(styles, { name: "CometAdminAlert" })(Alert); + +export { AdminComponentWithStyles as Alert }; + +declare module "@mui/material/styles" { + interface ComponentsPropsList { + CometAdminAlert: AlertProps; + } + + interface ComponentNameToClassKey { + CometAdminAlert: AlertClassKey; + } + + interface Components { + CometAdminAlert?: { + defaultProps?: ComponentsPropsList["CometAdminAlert"]; + styleOverrides?: ComponentNameToClassKey["CometAdminAlert"]; + }; + } +} diff --git a/packages/admin/admin/src/index.ts b/packages/admin/admin/src/index.ts index efe9813c84..d8e838b870 100644 --- a/packages/admin/admin/src/index.ts +++ b/packages/admin/admin/src/index.ts @@ -1,3 +1,4 @@ +export { Alert, AlertClassKey, AlertProps } from "./alert/Alert"; export { useFocusAwarePolling } from "./apollo/useFocusAwarePolling"; export { AppHeader, AppHeaderClassKey } from "./appHeader/AppHeader"; export { AppHeaderButton, AppHeaderButtonProps } from "./appHeader/button/AppHeaderButton"; diff --git a/packages/admin/cms-admin/src/dam/FileForm/EditFile.tsx b/packages/admin/cms-admin/src/dam/FileForm/EditFile.tsx index e5826151ea..4523b0d61a 100644 --- a/packages/admin/cms-admin/src/dam/FileForm/EditFile.tsx +++ b/packages/admin/cms-admin/src/dam/FileForm/EditFile.tsx @@ -191,7 +191,6 @@ const EditFileInner = ({ file, id }: EditFileInnerProps) => { }, }} initialValuesEqual={(prevValues, newValues) => isEqual(prevValues, newValues)} - validateOnBlur > {({ pristine, hasValidationErrors, submitting, handleSubmit }) => ( <> diff --git a/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx b/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx index 08c21addcf..ebdd5c71f7 100644 --- a/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx +++ b/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx @@ -59,6 +59,18 @@ export const FileSettingsFields = ({ isImage, folderId }: SettingsFormProps): Re [apollo, folderId, scope], ); + const requiredValidator = React.useCallback( + (value: unknown, allValues: object) => { + const type = (allValues as EditFileFormValues).license?.type; + const isRequired = type === "ROYALTY_FREE" ? false : damConfig.requireLicense; + + if (isRequired && !value) { + return ; + } + }, + [damConfig.requireLicense], + ); + return (
@@ -122,10 +134,9 @@ export const FileSettingsFields = ({ isImage, folderId }: SettingsFormProps): Re } }} shouldShowError={() => true} - validateFields={["license.durationTo"]} /> - {({ input: { value } }) => { + {({ input: { value: licenseType } }) => { return ( <> true} /> true} /> } fullWidth - disabled={value === "NO_LICENSE"} - required={damConfig.requireLicense} + disabled={licenseType === "NO_LICENSE"} > } - disabled={value === "NO_LICENSE"} - required={damConfig.requireLicense} + validateFields={["license.durationTo"]} + disabled={licenseType === "NO_LICENSE"} + validate={requiredValidator} shouldShowError={() => true} /> } validate={(value: Date | undefined, allValues) => { - if (value && allValues && value < (allValues as EditFileFormValues).license?.durationFrom) { + const requiredError = requiredValidator(value, allValues); + if (requiredError) { + return requiredError; + } + + const durationFrom = (allValues as EditFileFormValues).license?.durationFrom; + if (value && durationFrom && value < durationFrom) { return ( true} /> diff --git a/packages/eslint-config/nextjs.js b/packages/eslint-config/nextjs.js index 88480dcc89..ff0fab8c27 100644 --- a/packages/eslint-config/nextjs.js +++ b/packages/eslint-config/nextjs.js @@ -16,6 +16,11 @@ module.exports = { importNames: ["default"], message: "Please use Image from @comet/cms-site instead", }, + { + name: "@mui/material", + importNames: ["Alert"], + message: "Please use Alert from @comet/admin instead", + }, ], }, ],