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",
+ },
],
},
],