diff --git a/.changeset/famous-pianos-happen.md b/.changeset/famous-pianos-happen.md deleted file mode 100644 index 5253083ba9..0000000000 --- a/.changeset/famous-pianos-happen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@comet/admin": patch ---- - -Fix display of warnings for forms that use both form-level and field-level validation diff --git a/.changeset/metal-bees-fix.md b/.changeset/metal-bees-fix.md deleted file mode 100644 index f1250c1c36..0000000000 --- a/.changeset/metal-bees-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@comet/cms-api": patch ---- - -Add fallback values for users created via ID token diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000000..0484a97840 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "dictionaryDefinitions": [ + { + "name": "cspellignore", + "path": "./.cspellignore", + "addWords": true + } + ], + "dictionaries": ["cspellignore"], + "ignorePaths": ["node_modules", "/.cspellignore"] +} diff --git a/project-words.txt b/.cspellignore similarity index 93% rename from project-words.txt rename to .cspellignore index 3b7f4f0fc0..b06874f41b 100644 --- a/project-words.txt +++ b/.cspellignore @@ -1,24 +1,25 @@ Aktuelles Betrieb blockname +brevo codemod codemods Embeddables +exceljs +exif GraphQLJSONObject imgproxy Logische Mikro Mimetypes -NestJS nestjs +NestJS +ormconfig pagetree pkey prebuild rgba subcomponent subpage +Traefik typesafe -exceljs -ormconfig -exif -brevo \ No newline at end of file diff --git a/.github/workflows/main-into-next-pr.yml b/.github/workflows/main-into-next-pr.yml index 990189733e..44b1f60f3b 100644 --- a/.github/workflows/main-into-next-pr.yml +++ b/.github/workflows/main-into-next-pr.yml @@ -3,6 +3,7 @@ on: push: branches: - main + workflow_dispatch: jobs: main-into-next: name: Create "Merge main into next" PR diff --git a/cspell.config.yaml b/cspell.config.yaml deleted file mode 100644 index c81b7d52f2..0000000000 --- a/cspell.config.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -$schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json -version: "0.2" -dictionaryDefinitions: - - name: project-words - path: "./project-words.txt" - addWords: true -dictionaries: - - project-words -ignorePaths: - - "node_modules" - - "/project-words.txt" diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx index 9099e3939f..bedb0ee65b 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -27,6 +27,7 @@ import ProductCategoriesPage from "@src/products/categories/ProductCategoriesPag import { CombinationFieldsTestProductsPage } from "@src/products/future/CombinationFieldsTestProductsPage"; import { CreateCapProductPage as FutureCreateCapProductPage } from "@src/products/future/CreateCapProductPage"; import { ManufacturersPage as FutureManufacturersPage } from "@src/products/future/ManufacturersPage"; +import { ProductCategoriesHandmadePage } from "@src/products/future/ProductCategoriesPage"; import { ProductsPage as FutureProductsPage, ProductsPage } from "@src/products/future/ProductsPage"; import { ProductsWithLowPricePage as FutureProductsWithLowPricePage } from "@src/products/future/ProductsWithLowPricePage"; import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/ManufacturersPage"; @@ -310,6 +311,14 @@ export const masterMenuData: MasterMenuData = [ component: ManufacturersHandmadePage, }, }, + { + type: "route", + primary: , + route: { + path: "/product-category-handmade", + component: ProductCategoriesHandmadePage, + }, + }, ], }, ], diff --git a/demo/admin/src/products/ManufacturerForm.tsx b/demo/admin/src/products/ManufacturerForm.tsx index 8bd7ae3228..ae1f6ff272 100644 --- a/demo/admin/src/products/ManufacturerForm.tsx +++ b/demo/admin/src/products/ManufacturerForm.tsx @@ -6,15 +6,15 @@ import { FinalForm, FinalFormInput, type FinalFormSubmitEvent, - FinalFormSwitch, Loading, messages, + SwitchField, TextField, useFormApiRef, useStackSwitchApi, } from "@comet/admin"; import { queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; -import { Collapse, Divider, FormControlLabel } from "@mui/material"; +import { Collapse, Divider } from "@mui/material"; import { type FormApi } from "final-form"; import isEqual from "lodash.isequal"; import { useMemo } from "react"; @@ -195,183 +195,171 @@ export function ManufacturerForm({ id }: FormProps) { initialValuesEqual={isEqual} //required to compare block data correctly subscription={{}} > - {() => ( + <> + {saveConflict.dialogs} <> - {saveConflict.dialogs} - <> -
- } /> -
-
} - supportText={} - collapsible={true} - initiallyExpanded={true} - > - } - /> - } - /> - } - /> - } - /> - - } - > - {(props) => ( - } - label={props.input.checked ? : } - /> - )} - - - {({ input: { value } }) => ( - - <> - - } - /> - - } - /> - - } - /> - - } - /> - - - )} - -
-
} - > - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - } - /> - } - /> -
- +
+ } /> +
+
} + supportText={} + collapsible={true} + initiallyExpanded={true} + > + } + /> + } + /> + } + /> + } + /> + + } + label={(checked) => (checked ? : )} + /> + + {({ input: { value } }) => ( + + <> + + } + /> + + } + /> + + } + /> + + } + /> + + + )} + +
+
} + > + } + /> + } + /> + } + /> + } + /> + } + /> + + } + /> + } + /> + } + /> +
- )} + ); } diff --git a/demo/admin/src/products/categories/ProductCategoriesGrid.tsx b/demo/admin/src/products/categories/ProductCategoriesGrid.tsx new file mode 100644 index 0000000000..2560af8a1c --- /dev/null +++ b/demo/admin/src/products/categories/ProductCategoriesGrid.tsx @@ -0,0 +1,209 @@ +import { gql, useApolloClient, useQuery } from "@apollo/client"; +import { + CrudContextMenu, + DataGridToolbar, + filterByFragment, + type GridColDef, + ToolbarActions, + useBufferedRowCount, + useDataGridRemote, + usePersistentColumnState, +} from "@comet/admin"; +import { useTheme } from "@mui/material"; +import { DataGridPro, type GridRenderCellParams, type GridRowOrderChangeParams, type GridToolbarProps } from "@mui/x-data-grid-pro"; +import { type ReactNode } from "react"; +import { useIntl } from "react-intl"; + +import { + type GQLCreateProductCategoryMutation, + type GQLCreateProductCategoryMutationVariables, + type GQLDeleteProductCategoryMutation, + type GQLDeleteProductCategoryMutationVariables, + type GQLProductCategoriesGridQuery, + type GQLProductCategoriesGridQueryVariables, + type GQLProductCategoryGridFutureFragment, + type GQLUpdateProductCategoryPositionMutation, + type GQLUpdateProductCategoryPositionMutationVariables, +} from "./ProductCategoriesGrid.generated"; + +const productCategoriesFragment = gql` + fragment ProductCategoryGridFuture on ProductCategory { + id + title + slug + position + } +`; + +const productCategoriesQuery = gql` + query ProductCategoriesGrid($offset: Int!, $limit: Int!, $sort: [ProductCategorySort!]) { + productCategories(offset: $offset, limit: $limit, sort: $sort) { + nodes { + ...ProductCategoryGridFuture + } + totalCount + } + } + ${productCategoriesFragment} +`; + +const deleteProductCategoryMutation = gql` + mutation DeleteProductCategory($id: ID!) { + deleteProductCategory(id: $id) + } +`; + +const createProductCategoryMutation = gql` + mutation CreateProductCategory($input: ProductCategoryInput!) { + createProductCategory(input: $input) { + id + } + } +`; + +const updateProductCategoryPositionMutation = gql` + mutation UpdateProductCategoryPosition($id: ID!, $input: ProductCategoryUpdateInput!) { + updateProductCategory(id: $id, input: $input) { + id + updatedAt + position + } + } +`; + +interface ProductCategoriesGridToolbarProps extends GridToolbarProps { + toolbarAction?: ReactNode; +} + +function ProductCategoriesGridToolbar({ toolbarAction }: ProductCategoriesGridToolbarProps) { + return ( + + {toolbarAction} + + ); +} + +type Props = { + toolbarAction?: ReactNode; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rowAction?: (params: GridRenderCellParams) => ReactNode; + actionsColumnWidth?: number; +}; + +export function ProductCategoriesGrid({ toolbarAction, rowAction, actionsColumnWidth = 52 }: Props) { + const client = useApolloClient(); + const intl = useIntl(); + const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("ProductCategoriesGrid") }; + + const handleRowOrderChange = async ({ row: { id }, targetIndex }: GridRowOrderChangeParams) => { + await client.mutate({ + mutation: updateProductCategoryPositionMutation, + variables: { id, input: { position: targetIndex + 1 } }, + awaitRefetchQueries: true, + refetchQueries: [productCategoriesQuery], + }); + }; + + const theme = useTheme(); + + const columns: GridColDef[] = [ + { + field: "title", + headerName: intl.formatMessage({ id: "productCategory.title", defaultMessage: "Titel" }), + flex: 1, + visible: theme.breakpoints.up("md"), + minWidth: 150, + filterable: false, + sortable: false, + }, + { + field: "slug", + headerName: intl.formatMessage({ id: "productCategory.slug", defaultMessage: "Slug" }), + flex: 1, + minWidth: 150, + filterable: false, + sortable: false, + }, + { + field: "position", + headerName: intl.formatMessage({ id: "productCategory.position", defaultMessage: "Position" }), + type: "number", + filterable: false, + sortable: false, + flex: 1, + minWidth: 150, + }, + { + field: "actions", + headerName: "", + sortable: false, + filterable: false, + type: "actions", + align: "right", + pinned: "right", + width: actionsColumnWidth, + renderCell: (params) => { + return ( + <> + {rowAction && rowAction(params)} + { + // Don't copy id, because we want to create a new entity with this data + const { id, ...filteredData } = filterByFragment(productCategoriesFragment, params.row); + return filteredData; + }} + onPaste={async ({ input }) => { + await client.mutate({ + mutation: createProductCategoryMutation, + variables: { input }, + }); + }} + onDelete={async () => { + await client.mutate({ + mutation: deleteProductCategoryMutation, + variables: { id: params.row.id }, + }); + }} + refetchQueries={[productCategoriesQuery]} + /> + + ); + }, + }, + ]; + + const { data, loading, error } = useQuery(productCategoriesQuery, { + variables: { + offset: 0, + limit: 100, + sort: { field: "position", direction: "ASC" }, + }, + }); + const rowCount = useBufferedRowCount(data?.productCategories.totalCount); + if (error) throw error; + const rows = + data?.productCategories.nodes.map((node) => ({ + ...node, + __reorder__: node.title, + })) ?? []; + + return ( + + ); +} diff --git a/demo/admin/src/products/future/ProductCategoriesPage.tsx b/demo/admin/src/products/future/ProductCategoriesPage.tsx new file mode 100644 index 0000000000..5727179dbc --- /dev/null +++ b/demo/admin/src/products/future/ProductCategoriesPage.tsx @@ -0,0 +1,23 @@ +import { Stack, StackMainContent, StackPage, StackSwitch, StackToolbar } from "@comet/admin"; +import { ContentScopeIndicator } from "@comet/cms-admin"; +import { ProductCategoriesGrid } from "@src/products/categories/ProductCategoriesGrid"; +import { useIntl } from "react-intl"; + +export function ProductCategoriesHandmadePage() { + const intl = useIntl(); + return ( + + + + } /> + + + + + + Add product category + + + + ); +} diff --git a/demo/admin/src/products/future/generated/ProductForm.tsx b/demo/admin/src/products/future/generated/ProductForm.tsx index 005ed964df..cce199edc9 100644 --- a/demo/admin/src/products/future/generated/ProductForm.tsx +++ b/demo/admin/src/products/future/generated/ProductForm.tsx @@ -191,7 +191,7 @@ export function ProductForm({ id }: FormProps) { {saveConflict.dialogs} <>
} supportText={ mode === "edit" && ( diff --git a/demo/admin/src/theme.ts b/demo/admin/src/theme.ts index 97979b9cdc..93bc781f05 100644 --- a/demo/admin/src/theme.ts +++ b/demo/admin/src/theme.ts @@ -1,3 +1,15 @@ -import { createCometTheme } from "@comet/admin"; +import { createCometTheme, DataGridPanel } from "@comet/admin"; +import type {} from "@mui/x-data-grid/themeAugmentation"; -export const theme = createCometTheme(); +export const theme = createCometTheme({ + components: { + MuiDataGrid: { + defaultProps: { + slots: { + // @ts-expect-error @jamesricky fix this please + panel: DataGridPanel, + }, + }, + }, + }, +}); diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json index 2471edd7ab..9b5ec165b6 100644 --- a/demo/api/block-meta.json +++ b/demo/api/block-meta.json @@ -653,15 +653,15 @@ "blocks": { "accordion": "Accordion", "anchor": "Anchor", - "space": "Space", - "teaser": "Teaser", - "richtext": "RichText", - "heading": "StandaloneHeading", - "columns": "Columns", "callToActionList": "StandaloneCallToActionList", + "columns": "Columns", + "heading": "StandaloneHeading", "keyFacts": "KeyFacts", "media": "StandaloneMedia", - "mediaGallery": "MediaGallery" + "mediaGallery": "MediaGallery", + "richtext": "RichText", + "space": "Space", + "teaser": "Teaser" }, "nullable": false } @@ -697,15 +697,15 @@ "blocks": { "accordion": "Accordion", "anchor": "Anchor", - "space": "Space", - "teaser": "Teaser", - "richtext": "RichText", - "heading": "StandaloneHeading", - "columns": "Columns", "callToActionList": "StandaloneCallToActionList", + "columns": "Columns", + "heading": "StandaloneHeading", "keyFacts": "KeyFacts", "media": "StandaloneMedia", - "mediaGallery": "MediaGallery" + "mediaGallery": "MediaGallery", + "richtext": "RichText", + "space": "Space", + "teaser": "Teaser" }, "nullable": false } diff --git a/demo/api/package.json b/demo/api/package.json index dfd1cd1484..96a059de0d 100644 --- a/demo/api/package.json +++ b/demo/api/package.json @@ -20,8 +20,6 @@ "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --project ./tsconfig.lint.json", "mikro-orm": "dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- mikro-orm", - "mikro-orm:drop": "mikro-orm schema:drop -r", - "mikro-orm:migration:generate": "mikro-orm migration:create", "start": "$npm_execpath prebuild && dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- nest start --debug --watch --preserveWatchOutput", "start:dev": "run-p dev:*", "start:prod": "node dist/main" @@ -50,6 +48,7 @@ "@sentry/node": "^9.2.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "cli-progress": "^3.12.0", "compression": "^1.8.0", "cookie-parser": "^1.4.7", "express": "^5.0.1", @@ -69,6 +68,7 @@ "@comet/eslint-config": "workspace:*", "@nestjs/cli": "^11.0.4", "@nestjs/schematics": "^11.0.1", + "@types/cli-progress": "^3.11.6", "@types/compression": "^1.7.5", "@types/cookie-parser": "^1.4.8", "@types/express": "^5.0.0", diff --git a/demo/api/schema.gql b/demo/api/schema.gql index 02499a1ae5..cb14bb634d 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -204,21 +204,53 @@ type UserPermissionPaginatedUserList { totalCount: Int! } -type Link implements DocumentInterface { +type DamScope { + domain: String! +} + +type DamFolder { id: ID! - updatedAt: DateTime! - content: LinkBlockData! + name: String! + numberOfChildFolders: Int! + numberOfFiles: Int! + mpath: [ID!]! + archived: Boolean! + isInboxFromOtherScope: Boolean! createdAt: DateTime! - pageTreeNode: PageTreeNode + updatedAt: DateTime! + scope: DamScope! + parent: DamFolder + parents: [DamFolder!]! } -interface DocumentInterface { +type DamFile { id: ID! + folder: DamFolder + name: String! + size: Int! + mimetype: String! + contentHash: String! + title: String + altText: String + archived: Boolean! + image: DamFileImage + license: DamFileLicense + createdAt: DateTime! updatedAt: DateTime! + importSourceId: String + importSourceType: String + scope: DamScope! + fileUrl: String! + duplicates: [DamFile!]! + damPath: String! + dependents(offset: Int! = 0, limit: Int! = 25, filter: DependentFilter, forceRefresh: Boolean! = false): PaginatedDependencies! } -"""Link root block data""" -scalar LinkBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") +input DependentFilter { + rootGraphqlObjectType: String + rootId: String + rootColumnName: String +} type Page implements DocumentInterface { id: ID! @@ -230,6 +262,11 @@ type Page implements DocumentInterface { pageTreeNode: PageTreeNode } +interface DocumentInterface { + id: ID! + updatedAt: DateTime! +} + """PageContent root block data""" scalar PageContentBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") @@ -396,6 +433,17 @@ A date string, such as 2007-12-03, compliant with the `full-date` format outline """ scalar Date +type Link implements DocumentInterface { + id: ID! + updatedAt: DateTime! + content: LinkBlockData! + createdAt: DateTime! + pageTreeNode: PageTreeNode +} + +"""Link root block data""" +scalar LinkBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") + type PageTreeNodeScope { domain: String! language: String! @@ -442,54 +490,6 @@ enum UserGroup { union PageContentUnion = Page | Link | PredefinedPage -input DependentFilter { - rootGraphqlObjectType: String - rootId: String - rootColumnName: String -} - -type DamScope { - domain: String! -} - -type DamFolder { - id: ID! - name: String! - numberOfChildFolders: Int! - numberOfFiles: Int! - mpath: [ID!]! - archived: Boolean! - isInboxFromOtherScope: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - scope: DamScope! - parent: DamFolder - parents: [DamFolder!]! -} - -type DamFile { - id: ID! - folder: DamFolder - name: String! - size: Int! - mimetype: String! - contentHash: String! - title: String - altText: String - archived: Boolean! - image: DamFileImage - license: DamFileLicense - createdAt: DateTime! - updatedAt: DateTime! - importSourceId: String - importSourceType: String - scope: DamScope! - fileUrl: String! - duplicates: [DamFile!]! - damPath: String! - dependents(offset: Int! = 0, limit: Int! = 25, filter: DependentFilter, forceRefresh: Boolean! = false): PaginatedDependencies! -} - type PredefinedPage implements DocumentInterface { id: ID! updatedAt: DateTime! @@ -686,6 +686,10 @@ type PaginatedDamFolders { totalCount: Int! } +input DamScopeInput { + domain: String! +} + input AlternativeAddressInput { street: String! streetNumber: Float @@ -737,10 +741,6 @@ input PageTreeNodeScopeInput { language: String! } -input DamScopeInput { - domain: String! -} - input FooterScopeInput { domain: String! language: String! diff --git a/demo/api/src/common/blocks/accordion-item.block.ts b/demo/api/src/common/blocks/accordion-item.block.ts index 0004165170..97fa489b83 100644 --- a/demo/api/src/common/blocks/accordion-item.block.ts +++ b/demo/api/src/common/blocks/accordion-item.block.ts @@ -17,7 +17,7 @@ import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-c import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block"; import { IsBoolean, IsString } from "class-validator"; -const AccordionContentBlock = createBlocksBlock( +export const AccordionContentBlock = createBlocksBlock( { supportedBlocks: { richtext: RichTextBlock, diff --git a/demo/api/src/common/blocks/call-to-action.block.ts b/demo/api/src/common/blocks/call-to-action.block.ts index 9dc54ca60b..3adc1791a1 100644 --- a/demo/api/src/common/blocks/call-to-action.block.ts +++ b/demo/api/src/common/blocks/call-to-action.block.ts @@ -13,7 +13,7 @@ import { IsEnum } from "class-validator"; import { TextLinkBlock } from "./text-link.block"; -enum Variant { +export enum Variant { Contained = "Contained", Outlined = "Outlined", Text = "Text", diff --git a/demo/api/src/common/blocks/heading.block.ts b/demo/api/src/common/blocks/heading.block.ts index f1aab09df9..d5a253d840 100644 --- a/demo/api/src/common/blocks/heading.block.ts +++ b/demo/api/src/common/blocks/heading.block.ts @@ -13,7 +13,7 @@ import { IsEnum } from "class-validator"; import { RichTextBlock } from "./rich-text.block"; -enum HeadlineTag { +export enum HeadlineTag { H1 = "H1", H2 = "H2", H3 = "H3", diff --git a/demo/api/src/common/blocks/media-gallery.block.ts b/demo/api/src/common/blocks/media-gallery.block.ts index 0b8238cb9c..3736d2e84c 100644 --- a/demo/api/src/common/blocks/media-gallery.block.ts +++ b/demo/api/src/common/blocks/media-gallery.block.ts @@ -14,7 +14,7 @@ import { MediaGalleryItemBlock } from "@src/common/blocks/media-gallery-item.blo import { MediaAspectRatios } from "@src/util/mediaAspectRatios"; import { IsEnum } from "class-validator"; -const MediaGalleryListBlock = createListBlock({ block: MediaGalleryItemBlock }, "MediaGalleryList"); +export const MediaGalleryListBlock = createListBlock({ block: MediaGalleryItemBlock }, "MediaGalleryList"); class MediaGalleryBlockData extends BlockData { @ChildBlock(MediaGalleryListBlock) diff --git a/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts index 241f27d850..34c1e37a2f 100644 --- a/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts +++ b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts @@ -12,7 +12,7 @@ import { import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block"; import { IsEnum } from "class-validator"; -enum Alignment { +export enum Alignment { left = "left", center = "center", right = "right", diff --git a/demo/api/src/common/blocks/standalone-heading.block.ts b/demo/api/src/common/blocks/standalone-heading.block.ts index 08f6d785aa..8b0a8eaddc 100644 --- a/demo/api/src/common/blocks/standalone-heading.block.ts +++ b/demo/api/src/common/blocks/standalone-heading.block.ts @@ -12,7 +12,7 @@ import { import { HeadingBlock } from "@src/common/blocks/heading.block"; import { IsEnum } from "class-validator"; -enum TextAlignment { +export enum TextAlignment { left = "left", center = "center", } diff --git a/demo/api/src/db/fixtures/generators/images/01.jpg b/demo/api/src/db/fixtures/assets/images/01.jpg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/01.jpg rename to demo/api/src/db/fixtures/assets/images/01.jpg diff --git a/demo/api/src/db/fixtures/generators/images/02.jpg b/demo/api/src/db/fixtures/assets/images/02.jpg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/02.jpg rename to demo/api/src/db/fixtures/assets/images/02.jpg diff --git a/demo/api/src/db/fixtures/generators/images/03.jpg b/demo/api/src/db/fixtures/assets/images/03.jpg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/03.jpg rename to demo/api/src/db/fixtures/assets/images/03.jpg diff --git a/demo/api/src/db/fixtures/generators/images/04.jpg b/demo/api/src/db/fixtures/assets/images/04.jpg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/04.jpg rename to demo/api/src/db/fixtures/assets/images/04.jpg diff --git a/demo/api/src/db/fixtures/generators/images/05.jpg b/demo/api/src/db/fixtures/assets/images/05.jpg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/05.jpg rename to demo/api/src/db/fixtures/assets/images/05.jpg diff --git a/demo/api/src/db/fixtures/generators/images/astronaut.png b/demo/api/src/db/fixtures/assets/images/astronaut.png similarity index 100% rename from demo/api/src/db/fixtures/generators/images/astronaut.png rename to demo/api/src/db/fixtures/assets/images/astronaut.png diff --git a/demo/api/src/db/fixtures/generators/images/comet.png b/demo/api/src/db/fixtures/assets/images/comet.png similarity index 100% rename from demo/api/src/db/fixtures/generators/images/comet.png rename to demo/api/src/db/fixtures/assets/images/comet.png diff --git a/demo/api/src/db/fixtures/generators/images/planet.png b/demo/api/src/db/fixtures/assets/images/planet.png similarity index 100% rename from demo/api/src/db/fixtures/generators/images/planet.png rename to demo/api/src/db/fixtures/assets/images/planet.png diff --git a/demo/api/src/db/fixtures/generators/images/rocket.png b/demo/api/src/db/fixtures/assets/images/rocket.png similarity index 100% rename from demo/api/src/db/fixtures/generators/images/rocket.png rename to demo/api/src/db/fixtures/assets/images/rocket.png diff --git a/demo/api/src/db/fixtures/generators/images/sun.png b/demo/api/src/db/fixtures/assets/images/sun.png similarity index 100% rename from demo/api/src/db/fixtures/generators/images/sun.png rename to demo/api/src/db/fixtures/assets/images/sun.png diff --git a/demo/api/src/db/fixtures/generators/images/comet-logo-claim.svg b/demo/api/src/db/fixtures/assets/svg/comet-logo-claim.svg similarity index 100% rename from demo/api/src/db/fixtures/generators/images/comet-logo-claim.svg rename to demo/api/src/db/fixtures/assets/svg/comet-logo-claim.svg diff --git a/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4 b/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4 new file mode 100644 index 0000000000..eb6bfea9d1 Binary files /dev/null and b/demo/api/src/db/fixtures/assets/videos/hd-1080-laptop.mp4 differ diff --git a/demo/api/src/db/fixtures/fixtures.command.ts b/demo/api/src/db/fixtures/fixtures.command.ts index d2f8a4e7ec..9403f7d537 100644 --- a/demo/api/src/db/fixtures/fixtures.command.ts +++ b/demo/api/src/db/fixtures/fixtures.command.ts @@ -2,26 +2,27 @@ import { BlobStorageBackendService, DependenciesService, PageTreeNodeInterface, import { faker } from "@faker-js/faker"; import { InjectRepository } from "@mikro-orm/nestjs"; import { CreateRequestContext, EntityManager, EntityRepository, MikroORM } from "@mikro-orm/postgresql"; -import { Inject } from "@nestjs/common"; +import { Inject, Logger } from "@nestjs/common"; import { Config } from "@src/config/config"; import { CONFIG } from "@src/config/config.module"; import { generateSeoBlock } from "@src/db/fixtures/generators/blocks/seo.generator"; -import { Link } from "@src/documents/links/entities/link.entity"; import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; import { StageBlock } from "@src/documents/pages/blocks/stage.block"; import { PageInput } from "@src/documents/pages/dto/page.input"; import { Page } from "@src/documents/pages/entities/page.entity"; -import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope"; import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category"; import { UserGroup } from "@src/user-groups/user-group"; +import { MultiBar, Options, Presets } from "cli-progress"; import { Command, CommandRunner } from "nest-commander"; import slugify from "slugify"; +import { DocumentGeneratorService } from "./generators/document-generator.service"; import { FileUploadsFixtureService } from "./generators/file-uploads-fixture.service"; -import { generateLinks } from "./generators/links.generator"; +import { ImageFixtureService } from "./generators/image-fixture.service"; import { ManyImagesTestPageFixtureService } from "./generators/many-images-test-page-fixture.service"; import { ProductsFixtureService } from "./generators/products-fixture.service"; import { RedirectsFixtureService } from "./generators/redirects-fixture.service"; +import { VideoFixtureService } from "./generators/video-fixture.service"; export interface PageTreeNodesFixtures { home?: PageTreeNodeInterface; @@ -45,190 +46,92 @@ const getDefaultPageInput = (): PageInput => { description: "Create fixtures with faker.js", }) export class FixturesCommand extends CommandRunner { + private readonly logger = new Logger(FixturesCommand.name); + + barOptions: Options = { + format: `{bar} {percentage}% | {value}/{total} {title} | ETA: {eta_formatted} | Duration: {duration_formatted}`, + noTTYOutput: true, + }; + constructor( @Inject(CONFIG) private readonly config: Config, private readonly blobStorageBackendService: BlobStorageBackendService, - private readonly pageTreeService: PageTreeService, - private readonly orm: MikroORM, - @InjectRepository(Page) private readonly pagesRepository: EntityRepository, - @InjectRepository(Link) private readonly linksRepository: EntityRepository, - private readonly manyImagesTestPageFixtureService: ManyImagesTestPageFixtureService, - private readonly fileUploadsFixtureService: FileUploadsFixtureService, - private readonly redirectsFixtureService: RedirectsFixtureService, + private readonly documentGeneratorService: DocumentGeneratorService, private readonly dependenciesService: DependenciesService, private readonly entityManager: EntityManager, private readonly productsFixtureService: ProductsFixtureService, + private readonly fileUploadsFixtureService: FileUploadsFixtureService, + private readonly imageFixtureService: ImageFixtureService, + private readonly manyImagesTestPageFixtureService: ManyImagesTestPageFixtureService, + private readonly orm: MikroORM, + @InjectRepository(Page) private readonly pagesRepository: EntityRepository, + private readonly pageTreeService: PageTreeService, + private readonly redirectsFixtureService: RedirectsFixtureService, + private readonly videoFixtureService: VideoFixtureService, ) { super(); } @CreateRequestContext() async run(): Promise { - const pageTreeNodes: PageTreeNodesFixtures = {}; // ensure repeatable runs faker.seed(123456); + this.logger.log("Drop tables..."); + const connection = this.orm.em.getConnection(); + const tables = await connection.execute(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' ORDER BY tablename;`); + + for (const table of tables) { + await connection.execute(`DROP TABLE IF EXISTS "${table.tablename}" CASCADE`); + } + + this.logger.log("Clear storage..."); const damFilesDirectory = `${this.config.blob.storageDirectoryPrefix}-files`; if (await this.blobStorageBackendService.folderExists(damFilesDirectory)) { await this.blobStorageBackendService.removeFolder(damFilesDirectory); } await this.blobStorageBackendService.createFolder(damFilesDirectory); - console.log("Storage cleared"); - const generator = this.orm.getSchemaGenerator(); - console.log(`Drop and recreate schema...`); - await generator.dropSchema({ dropDb: false, dropMigrationsTable: true }); - - console.log(`Run migrations...`); + this.logger.log("Run migrations..."); const migrator = this.orm.getMigrator(); await migrator.up(); - const scope: PageTreeNodeScope = { - domain: "main", - language: "en", - }; + const scope = { domain: "main", language: "en" }; - const attachedDocumentIds = [ - "b0bf3927-e080-4d4e-997b-d7f18b051e0f", - "4ff8707a-4bb7-45ab-b06d-72a3e94286c7", - "729c37f5-c28b-45d4-88a8-a89abc579c07", - "46ce964c-f029-46f0-9961-ef436e2391f2", - "dc956c9a-729b-4d8e-9ed4-f1be1dad6dd3", - ]; + const multiBar = new MultiBar(this.barOptions, Presets.shades_classic); - let node = await this.pageTreeService.createNode( - { - name: "Home", - slug: "home", - attachedDocument: { - 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, - ); - pageTreeNodes.home = node; + this.logger.log("Generate Images..."); + await this.imageFixtureService.generateImages(5, { domain: "main" }); - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); + this.logger.log("Generate Videos..."); + await this.videoFixtureService.generateVideos({ domain: "main" }); - node = await this.pageTreeService.createNode( - { - id: "aaa585d3-eca1-47c9-8852-9370817b49ac", - 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, - ); - pageTreeNodes.sub = node; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); - - node = await this.pageTreeService.createNode( - { - name: "Test 2", - slug: "test2", - attachedDocument: { - 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, - ); - pageTreeNodes.test2 = node; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); + this.logger.log("Generate Pages..."); + await this.documentGeneratorService.generatePage({ name: "Home", scope }); + const blockCategoriesPage = await this.documentGeneratorService.generatePage({ name: "Fixtures: Blocks", scope }); - node = await this.pageTreeService.createNode( - { - name: "Test 3", - slug: "test3", - 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, + await this.documentGeneratorService.generatePage({ name: "Layout", scope, blockCategory: "layout", parentId: blockCategoriesPage.id }); + await this.documentGeneratorService.generatePage({ name: "Media", scope, blockCategory: "media", parentId: blockCategoriesPage.id }); + await this.documentGeneratorService.generatePage({ + name: "Navigation", scope, - ); - pageTreeNodes.test3 = node; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); - - node = await this.pageTreeService.createNode( - { - name: "Link1", - slug: "link", - attachedDocument: { - 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, - ); - pageTreeNodes.link1 = node; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); - - node = await this.pageTreeService.createNode( - { - name: "Test Site visibility", - slug: "test-site-visibility", - attachedDocument: { - 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, + blockCategory: "navigation", + parentId: blockCategoriesPage.id, + }); + await this.documentGeneratorService.generatePage({ name: "Teaser", scope, blockCategory: "teaser", parentId: blockCategoriesPage.id }); + await this.documentGeneratorService.generatePage({ + name: "Text and Content", scope, - ); - pageTreeNodes.testSiteVisibility = node; - - await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); - - for (const attachedDocumentId of attachedDocumentIds) { - const pageInput = getDefaultPageInput(); - - await this.entityManager.persistAndFlush( - this.pagesRepository.create({ - id: attachedDocumentId, - createdAt: new Date(), - updatedAt: new Date(), - content: pageInput.content.transformToBlockData(), - seo: pageInput.seo.transformToBlockData(), - stage: pageInput.stage.transformToBlockData(), - }), - ); - } + blockCategory: "textAndContent", + parentId: blockCategoriesPage.id, + }); - console.log("generate links"); - await generateLinks(this.linksRepository, pageTreeNodes); - console.log("links generated"); - - console.log("generate many images test page"); + this.logger.log("Generate Many Images Test Page..."); await this.manyImagesTestPageFixtureService.execute(); - console.log("many images test page created"); - - console.log("generate lorem ispum fixtures"); + this.logger.log("Many Images Test Page created"); + this.logger.log("Generate Lorem Ispum Fixtures..."); const NUMBER_OF_DOMAINS_WITH_LORUM_IPSUM_CONTENT = 0; // Increase number to generate lorum ipsum fixtures - for (let domainNum = 0; domainNum < NUMBER_OF_DOMAINS_WITH_LORUM_IPSUM_CONTENT; domainNum++) { const domain = domainNum === 0 ? "secondary" : `${faker.lorem.word().toLowerCase()}.com`; let pagesCount = 0; @@ -282,13 +185,17 @@ export class FixturesCommand extends CommandRunner { pages.push(pagesForLevel); } - console.log(`Generated ${pagesCount} lorem ipsum pages for ${domain}`); + this.logger.log(`Generated ${pagesCount} lorem ipsum pages for ${domain}`); } + this.logger.log("Generate File Uploads..."); await this.fileUploadsFixtureService.generateFileUploads(); + this.logger.log("Generate Redirects..."); await this.redirectsFixtureService.generateRedirects(); + multiBar.stop(); + await this.dependenciesService.createViews(); await this.productsFixtureService.generate(); diff --git a/demo/api/src/db/fixtures/fixtures.module.ts b/demo/api/src/db/fixtures/fixtures.module.ts index 92b7e752c8..fb562dd249 100644 --- a/demo/api/src/db/fixtures/fixtures.module.ts +++ b/demo/api/src/db/fixtures/fixtures.module.ts @@ -2,6 +2,7 @@ import { DependenciesModule } from "@comet/cms-api"; import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; import { ConfigModule } from "@src/config/config.module"; +import { DamFile } from "@src/dam/entities/dam-file.entity"; import { FixturesCommand } from "@src/db/fixtures/fixtures.command"; import { Link } from "@src/documents/links/entities/link.entity"; import { LinksModule } from "@src/documents/links/links.module"; @@ -10,23 +11,96 @@ import { PagesModule } from "@src/documents/pages/pages.module"; import { Manufacturer } from "@src/products/entities/manufacturer.entity"; import { Product } from "@src/products/entities/product.entity"; +import { AccordionBlockFixtureService } from "./generators/blocks/layout/accordion-block-fixture.service"; +import { ColumnsBlockFixtureService } from "./generators/blocks/layout/columns-block-fixture.service"; +import { ContentGroupBlockFixtureService } from "./generators/blocks/layout/content-group-block-fixture.service"; +import { LayoutBlockFixtureService } from "./generators/blocks/layout/layout-block-fixture.service"; +import { SpaceBlockFixtureService } from "./generators/blocks/layout/space-block-fixture.service"; +import { DamImageBlockFixtureService } from "./generators/blocks/media/dam-image-block-fixture.service"; +import { DamVideoBlockFixtureService } from "./generators/blocks/media/dam-video-block-fixture.service"; +import { FullWidthImageBlockFixtureService } from "./generators/blocks/media/full-width-image-block-fixture.service"; +import { MediaBlockFixtureService } from "./generators/blocks/media/media-block.fixture.service"; +import { MediaGalleryBlockFixtureService } from "./generators/blocks/media/media-gallery-block-fixture.service"; +import { PixelImageBlockFixtureService } from "./generators/blocks/media/pixel-image-block-fixture.service"; +import { StandaloneMediaBlockFixtureService } from "./generators/blocks/media/standalone-media-block-fixture.service"; +import { SvgImageBlockFixtureService } from "./generators/blocks/media/svg-image-block-fixture.service"; +import { VimeoVideoBlockFixtureService } from "./generators/blocks/media/vimeo-video-block-fixture.service"; +import { YouTubeVideoBlockFixtureService } from "./generators/blocks/media/youtube-video-block-fixture.service"; +import { AnchorBlockFixtureService } from "./generators/blocks/navigation/anchor-block-fixture.service"; +import { CallToActionBlockFixtureService } from "./generators/blocks/navigation/call-to-action-block-fixture.service"; +import { CallToActionListBlockFixtureService } from "./generators/blocks/navigation/call-to-action-list-block.service"; +import { LinkBlockFixtureService } from "./generators/blocks/navigation/link-block-fixture.service"; +import { LinkListBlockFixtureService } from "./generators/blocks/navigation/link-list-block-fixture.service"; +import { StandaloneCallToActionListBlockFixtureService } from "./generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service"; +import { TextLinkBlockFixtureService } from "./generators/blocks/navigation/text-link-block-fixture.service"; +import { BasicStageBlockFixtureService } from "./generators/blocks/stage/basic-stage-block-fixture.service"; +import { BillboardTeaserBlockFixtureService } from "./generators/blocks/teaser/billboard-teaser-block-fixture.service"; +import { TeaserBlockFixtureService } from "./generators/blocks/teaser/teaser-block-fixture.service"; +import { HeadingBlockFixtureService } from "./generators/blocks/text-and-content/heading-block-fixture.service"; +import { KeyFactsBlockFixtureService } from "./generators/blocks/text-and-content/key-facts-block-fixture.service"; +import { RichTextBlockFixtureService } from "./generators/blocks/text-and-content/rich-text-block-fixture.service"; +import { StandaloneHeadingBlockFixtureService } from "./generators/blocks/text-and-content/standalone-heading-block-fixture.service"; +import { TextImageBlockFixtureService } from "./generators/blocks/text-and-content/text-image-block-fixture.service"; +import { DocumentGeneratorService } from "./generators/document-generator.service"; import { FileUploadsFixtureService } from "./generators/file-uploads-fixture.service"; import { ImageFileFixtureService } from "./generators/image-file-fixture.service"; +import { ImageFixtureService } from "./generators/image-fixture.service"; import { ManyImagesTestPageFixtureService } from "./generators/many-images-test-page-fixture.service"; +import { PageContentBlockFixtureService } from "./generators/page-content-block-fixture.service"; import { ProductsFixtureService } from "./generators/products-fixture.service"; import { RedirectsFixtureService } from "./generators/redirects-fixture.service"; +import { SeoBlockFixtureService } from "./generators/seo-block-fixture.service"; +import { StageBlockFixtureService } from "./generators/stage-block-fixture.service"; import { SvgImageFileFixtureService } from "./generators/svg-image-file-fixture.service"; +import { VideoFixtureService } from "./generators/video-fixture.service"; @Module({ - imports: [ConfigModule, PagesModule, LinksModule, DependenciesModule, MikroOrmModule.forFeature([Page, Link, Product, Manufacturer])], + imports: [ConfigModule, PagesModule, LinksModule, DependenciesModule, MikroOrmModule.forFeature([DamFile, Page, Link, Product, Manufacturer])], providers: [ FixturesCommand, - ManyImagesTestPageFixtureService, - ImageFileFixtureService, - SvgImageFileFixtureService, + AccordionBlockFixtureService, + AnchorBlockFixtureService, + BasicStageBlockFixtureService, + BillboardTeaserBlockFixtureService, + CallToActionBlockFixtureService, + CallToActionListBlockFixtureService, + ColumnsBlockFixtureService, + ContentGroupBlockFixtureService, + DamImageBlockFixtureService, + DamVideoBlockFixtureService, + DocumentGeneratorService, FileUploadsFixtureService, + FullWidthImageBlockFixtureService, + HeadingBlockFixtureService, + ImageFileFixtureService, + ImageFixtureService, + KeyFactsBlockFixtureService, + LayoutBlockFixtureService, + LinkBlockFixtureService, + LinkListBlockFixtureService, + ManyImagesTestPageFixtureService, + MediaGalleryBlockFixtureService, + MediaBlockFixtureService, + PageContentBlockFixtureService, + PixelImageBlockFixtureService, RedirectsFixtureService, ProductsFixtureService, + RichTextBlockFixtureService, + SeoBlockFixtureService, + SpaceBlockFixtureService, + StageBlockFixtureService, + SvgImageBlockFixtureService, + SvgImageFileFixtureService, + StageBlockFixtureService, + StandaloneCallToActionListBlockFixtureService, + StandaloneHeadingBlockFixtureService, + StandaloneMediaBlockFixtureService, + TeaserBlockFixtureService, + TextImageBlockFixtureService, + TextLinkBlockFixtureService, + VideoFixtureService, + VimeoVideoBlockFixtureService, + YouTubeVideoBlockFixtureService, ], }) export class FixturesModule {} diff --git a/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts b/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts new file mode 100644 index 0000000000..d94a458287 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/block-fixture.ts @@ -0,0 +1,3 @@ +import { type Block, type ExtractBlockInputFactoryProps } from "@comet/cms-api"; + +export type BlockFixture = { generateBlockInput: () => Promise> }; diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts new file mode 100644 index 0000000000..94091cebcb --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/accordion-block-fixture.service.ts @@ -0,0 +1,73 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { AccordionBlock } from "@src/common/blocks/accordion.block"; +import { AccordionContentBlock, AccordionItemBlock } from "@src/common/blocks/accordion-item.block"; + +import { BlockFixture } from "../block-fixture"; +import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; +import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service"; +import { SpaceBlockFixtureService } from "./space-block-fixture.service"; + +@Injectable() +export class AccordionBlockFixtureService { + constructor( + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService, + private readonly spaceBlockFixtureService: SpaceBlockFixtureService, + private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService, + ) {} + + async generateAccordionContentBlock(): Promise> { + const blocks: ExtractBlockInputFactoryProps["blocks"] = []; + const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = { + richtext: this.richTextBlockFixtureService, + heading: this.headingBlockFixtureService, + space: this.spaceBlockFixtureService, + callToActionList: this.callToActionListBlockFixtureService, + }; + + for (const block of Object.entries(blockCfg)) { + const [type, generator] = block; + const props = await generator.generateBlockInput(); + + blocks.push({ + key: faker.string.uuid(), + visible: true, + type: type as keyof typeof blockCfg, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + props: props as any, + }); + } + + return { + blocks, + }; + } + + async generateAccordionItemBlock(): Promise> { + return { + title: faker.lorem.words({ min: 3, max: 9 }), + content: await this.generateAccordionContentBlock(), + openByDefault: faker.datatype.boolean(), + }; + } + + async generateBlockInput(min = 2, max = 6): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.generateAccordionItemBlock(), + }); + } + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts new file mode 100644 index 0000000000..5c4561e13f --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/columns-block-fixture.service.ts @@ -0,0 +1,75 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { ColumnsBlock, ColumnsContentBlock } from "@src/documents/pages/blocks/columns.block"; + +import { BlockFixture } from "../block-fixture"; +import { MediaGalleryBlockFixtureService } from "../media/media-gallery-block-fixture.service"; +import { StandaloneMediaBlockFixtureService } from "../media/standalone-media-block-fixture.service"; +import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; +import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service"; +import { AccordionBlockFixtureService } from "./accordion-block-fixture.service"; +import { SpaceBlockFixtureService } from "./space-block-fixture.service"; + +const oneColumnLayouts = [{ name: "2-20-2" }]; + +@Injectable() +export class ColumnsBlockFixtureService { + constructor( + private readonly accordionBlockFixtureService: AccordionBlockFixtureService, + private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService, + private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService, + private readonly richtextBlockFixtureService: RichTextBlockFixtureService, + private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService, + private readonly spaceBlockFixtureService: SpaceBlockFixtureService, + private readonly standaloneMediaBlockFixtureService: StandaloneMediaBlockFixtureService, + ) {} + + async generateColumnsContentBlock(): Promise> { + const blocks: ExtractBlockInputFactoryProps["blocks"] = []; + + const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = { + accordion: this.accordionBlockFixtureService, + anchor: this.headingBlockFixtureService, + callToActionList: this.callToActionListBlockFixtureService, + heading: this.headingBlockFixtureService, + media: this.standaloneMediaBlockFixtureService, + mediaGallery: this.mediaGalleryBlockFixtureService, + richtext: this.richtextBlockFixtureService, + space: this.spaceBlockFixtureService, + }; + + for (const block of Object.entries(blockCfg)) { + const [type, generator] = block; + const props = await generator.generateBlockInput(); + + blocks.push({ + key: faker.string.uuid(), + visible: true, + type: type as keyof typeof blockCfg, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + props: props as any, + }); + } + + return { + blocks: blocks, + }; + } + + async generateBlockInput(): Promise> { + const content = await this.generateColumnsContentBlock(); + + return { + layout: faker.helpers.arrayElement(oneColumnLayouts).name, + columns: [ + { + key: faker.string.uuid(), + visible: true, + props: content, + }, + ], + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts new file mode 100644 index 0000000000..eb65d2aa70 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/content-group-block-fixture.service.ts @@ -0,0 +1,76 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { BackgroundColor as ContentGroupBackgroundColor, ContentBlock, ContentGroupBlock } from "@src/documents/pages/blocks/content-group.block"; + +import { BlockFixture } from "../block-fixture"; +import { MediaGalleryBlockFixtureService } from "../media/media-gallery-block-fixture.service"; +import { StandaloneMediaBlockFixtureService } from "../media/standalone-media-block-fixture.service"; +import { AnchorBlockFixtureService } from "../navigation/anchor-block-fixture.service"; +import { StandaloneCallToActionListBlockFixtureService } from "../navigation/standalone-call-to-action-list-block-fixture.service"; +import { TeaserBlockFixtureService } from "../teaser/teaser-block-fixture.service"; +import { KeyFactsBlockFixtureService } from "../text-and-content/key-facts-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; +import { StandaloneHeadingBlockFixtureService } from "../text-and-content/standalone-heading-block-fixture.service"; +import { AccordionBlockFixtureService } from "./accordion-block-fixture.service"; +import { ColumnsBlockFixtureService } from "./columns-block-fixture.service"; +import { SpaceBlockFixtureService } from "./space-block-fixture.service"; + +@Injectable() +export class ContentGroupBlockFixtureService { + constructor( + private readonly accordionBlockFixtureService: AccordionBlockFixtureService, + private readonly anchorBlockFixtureService: AnchorBlockFixtureService, + private readonly columnsBlockFixtureService: ColumnsBlockFixtureService, + private readonly keyFactsBlockFixtureService: KeyFactsBlockFixtureService, + private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + private readonly spaceBlockFixtureService: SpaceBlockFixtureService, + private readonly standaloneMediaBlockFixtureService: StandaloneMediaBlockFixtureService, + private readonly standaloneHeadingBlockFixtureService: StandaloneHeadingBlockFixtureService, + private readonly standaloneCallToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService, + private readonly teaserBlockFixtureService: TeaserBlockFixtureService, + ) {} + + async generateContentGroupContentBlock(): Promise> { + const blocks: ExtractBlockInputFactoryProps["blocks"] = []; + + const blockCfg: Record<(typeof blocks)[number]["type"], BlockFixture> = { + accordion: this.accordionBlockFixtureService, + anchor: this.anchorBlockFixtureService, + callToActionList: this.standaloneCallToActionListBlockFixtureService, + columns: this.columnsBlockFixtureService, + heading: this.standaloneHeadingBlockFixtureService, + keyFacts: this.keyFactsBlockFixtureService, + media: this.standaloneMediaBlockFixtureService, + mediaGallery: this.mediaGalleryBlockFixtureService, + richtext: this.richTextBlockFixtureService, + space: this.spaceBlockFixtureService, + teaser: this.teaserBlockFixtureService, + }; + + for (const block of Object.entries(blockCfg)) { + const [type, generator] = block; + const props = await generator.generateBlockInput(); + + blocks.push({ + key: faker.string.uuid(), + visible: true, + type: type as keyof typeof blockCfg, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + props: props as any, + }); + } + + return { + blocks, + }; + } + + async generateBlockInput(): Promise> { + return { + backgroundColor: faker.helpers.arrayElement(Object.values(ContentGroupBackgroundColor)), + content: await this.generateContentGroupContentBlock(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts new file mode 100644 index 0000000000..4cb287d12f --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/layout-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { LayoutBlock, LayoutBlockLayout } from "@src/documents/pages/blocks/layout.block"; + +import { MediaBlockFixtureService } from "../media/media-block.fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; + +@Injectable() +export class LayoutBlockFixtureService { + constructor( + private readonly mediaBlockFixtureService: MediaBlockFixtureService, + private readonly richTextFixtureService: RichTextBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + return { + layout: faker.helpers.arrayElement(Object.values(LayoutBlockLayout)), + media1: await this.mediaBlockFixtureService.generateBlockInput(), + text1: await this.richTextFixtureService.generateBlockInput(), + media2: await this.mediaBlockFixtureService.generateBlockInput(), + text2: await this.richTextFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts new file mode 100644 index 0000000000..810c827d30 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/layout/space-block-fixture.service.ts @@ -0,0 +1,11 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { SpaceBlock, Spacing } from "@src/common/blocks/space.block"; + +@Injectable() +export class SpaceBlockFixtureService { + async generateBlockInput(): Promise> { + return { spacing: faker.helpers.arrayElement(Object.values(Spacing)) }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts new file mode 100644 index 0000000000..9db356eb9f --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-image-block-fixture.service.ts @@ -0,0 +1,36 @@ +import { DamImageBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service"; +import { SvgImageBlockFixtureService } from "./svg-image-block-fixture.service"; + +interface GenerateDamImageBlockInputProps { + generateSvgImage?: boolean; +} + +@Injectable() +export class DamImageBlockFixtureService { + constructor( + private readonly svgImageBlockFixtureService: SvgImageBlockFixtureService, + private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService, + ) {} + + async generateBlockInput(settings?: GenerateDamImageBlockInputProps): Promise> { + const types = ["pixelImage", "svgImage"] as const; + const type = settings?.generateSvgImage === true ? faker.helpers.arrayElement(types) : "pixelImage"; + + switch (type) { + case "svgImage": + return { + attachedBlocks: [{ type, props: await this.svgImageBlockFixtureService.generateBlockInput() }], + activeType: type, + }; + case "pixelImage": + return { + attachedBlocks: [{ type, props: await this.pixelImageBlockFixtureService.generateBlockInput() }], + activeType: type, + }; + } + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts new file mode 100644 index 0000000000..8386db2ae6 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/dam-video-block-fixture.service.ts @@ -0,0 +1,27 @@ +import { DamVideoBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +import { VideoFixtureService } from "../../video-fixture.service"; +import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service"; + +@Injectable() +export class DamVideoBlockFixtureService { + constructor( + private readonly videoFixtureService: VideoFixtureService, + private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + const autoplay = faker.datatype.boolean(); + const damFileId = this.videoFixtureService.getRandomVideo().id; + + return { + autoplay, + loop: faker.datatype.boolean(), + showControls: !autoplay, + damFileId, + previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts new file mode 100644 index 0000000000..1ccf21a5e8 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/full-width-image-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { FullWidthImageBlock } from "@src/documents/pages/blocks/full-width-image.block"; + +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; +import { DamImageBlockFixtureService } from "./dam-image-block-fixture.service"; + +@Injectable() +export class FullWidthImageBlockFixtureService { + constructor( + private readonly damImageBlockFixtureService: DamImageBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + return { + content: { + ...(await this.richTextBlockFixtureService.generateBlockInput()), + visible: faker.datatype.boolean({ probability: 1.0 }), + }, + image: await this.damImageBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts new file mode 100644 index 0000000000..fcfc95aa23 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/media-block.fixture.service.ts @@ -0,0 +1,46 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { MediaBlock } from "@src/common/blocks/media.block"; + +import { DamImageBlockFixtureService } from "./dam-image-block-fixture.service"; +import { DamVideoBlockFixtureService } from "./dam-video-block-fixture.service"; +import { VimeoVideoBlockFixtureService } from "./vimeo-video-block-fixture.service"; +import { YouTubeVideoBlockFixtureService } from "./youtube-video-block-fixture.service"; + +@Injectable() +export class MediaBlockFixtureService { + constructor( + private readonly damImageBlockFixtureService: DamImageBlockFixtureService, + private readonly damVideoBlockFixtureService: DamVideoBlockFixtureService, + private readonly youtubeVideoBlockFixtureService: YouTubeVideoBlockFixtureService, + private readonly vimeoVideoBlockFixtureService: VimeoVideoBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + const types = ["image", "damVideo", "youTubeVideo", "vimeoVideo"] as const; + const type = faker.helpers.arrayElement(types); + + return { + attachedBlocks: [ + { + type: "image", + props: await this.damImageBlockFixtureService.generateBlockInput(), + }, + { + type: "damVideo", + props: await this.damVideoBlockFixtureService.generateBlockInput(), + }, + { + type: "youTubeVideo", + props: await this.youtubeVideoBlockFixtureService.generateBlockInput(), + }, + { + type: "vimeoVideo", + props: await this.vimeoVideoBlockFixtureService.generateBlockInput(), + }, + ], + activeType: type, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts new file mode 100644 index 0000000000..0466797c41 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/media-gallery-block-fixture.service.ts @@ -0,0 +1,44 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { MediaGalleryBlock, MediaGalleryListBlock } from "@src/common/blocks/media-gallery.block"; +import { MediaGalleryItemBlock } from "@src/common/blocks/media-gallery-item.block"; +import { MediaAspectRatios } from "@src/util/mediaAspectRatios"; + +import { MediaBlockFixtureService } from "./media-block.fixture.service"; + +@Injectable() +export class MediaGalleryBlockFixtureService { + constructor(private readonly mediaBlockFixtureService: MediaBlockFixtureService) {} + + async generateMediaGalleryItemBlock(): Promise> { + return { + media: await this.mediaBlockFixtureService.generateBlockInput(), + caption: faker.lorem.words({ min: 3, max: 9 }), + }; + } + + async generateMediaGalleryListBlock(min = 2, max = 6): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.generateMediaGalleryItemBlock(), + }); + } + + return { + blocks: blocks, + }; + } + + async generateBlockInput(): Promise> { + return { + aspectRatio: faker.helpers.arrayElement(Object.values(MediaAspectRatios)), + items: await this.generateMediaGalleryListBlock(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts new file mode 100644 index 0000000000..00348b0348 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/pixel-image-block-fixture.service.ts @@ -0,0 +1,29 @@ +import { ExtractBlockInputFactoryProps, FocalPoint, ImageCropAreaInput, PixelImageBlock } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +import { ImageFixtureService } from "../../image-fixture.service"; + +@Injectable() +export class PixelImageBlockFixtureService { + constructor(private readonly imageFixtureService: ImageFixtureService) {} + + async generateBlockInput(): Promise> { + return { + damFileId: this.imageFixtureService.getRandomPixelImage().id, + cropArea: this.calculateDefaultCropInput(), + }; + } + + private calculateDefaultCropInput(): ImageCropAreaInput { + const focalPoint = faker.helpers.arrayElement(Object.values(Object.keys(FocalPoint))) as FocalPoint; + + return { + focalPoint, + x: focalPoint !== FocalPoint.SMART ? 0 : undefined, + y: focalPoint !== FocalPoint.SMART ? 0 : undefined, + height: focalPoint !== FocalPoint.SMART ? faker.number.int({ min: 20, max: 100 }) : undefined, + width: focalPoint !== FocalPoint.SMART ? faker.number.int({ min: 20, max: 100 }) : undefined, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts new file mode 100644 index 0000000000..d16ade2de8 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/standalone-media-block-fixture.service.ts @@ -0,0 +1,19 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block"; +import { MediaAspectRatios } from "@src/util/mediaAspectRatios"; + +import { MediaBlockFixtureService } from "./media-block.fixture.service"; + +@Injectable() +export class StandaloneMediaBlockFixtureService { + constructor(private readonly mediaBlockFixtureService: MediaBlockFixtureService) {} + + async generateBlockInput(): Promise> { + return { + media: await this.mediaBlockFixtureService.generateBlockInput(), + aspectRatio: faker.helpers.arrayElement(Object.values(MediaAspectRatios)), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts new file mode 100644 index 0000000000..b74bd6dbac --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/svg-image-block-fixture.service.ts @@ -0,0 +1,15 @@ +import { ExtractBlockInputFactoryProps, SvgImageBlock } from "@comet/cms-api"; +import { Injectable } from "@nestjs/common"; + +import { ImageFixtureService } from "../../image-fixture.service"; + +@Injectable() +export class SvgImageBlockFixtureService { + constructor(private readonly imageFixtureService: ImageFixtureService) {} + + async generateBlockInput(): Promise> { + return { + damFileId: this.imageFixtureService.getRandomSvg().id, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts new file mode 100644 index 0000000000..b279e48e7c --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/vimeo-video-block-fixture.service.ts @@ -0,0 +1,23 @@ +import { ExtractBlockInputFactoryProps, VimeoVideoBlock } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service"; + +@Injectable() +export class VimeoVideoBlockFixtureService { + constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {} + + async generateBlockInput(): Promise> { + const identifier = ["76979871"]; + const autoplay = faker.datatype.boolean(); + + return { + autoplay, + loop: faker.datatype.boolean(), + showControls: !autoplay, + vimeoIdentifier: faker.helpers.arrayElement(identifier), + previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts new file mode 100644 index 0000000000..5a0cf9fb62 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/media/youtube-video-block-fixture.service.ts @@ -0,0 +1,23 @@ +import { ExtractBlockInputFactoryProps, YouTubeVideoBlock } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +import { PixelImageBlockFixtureService } from "./pixel-image-block-fixture.service"; + +@Injectable() +export class YouTubeVideoBlockFixtureService { + constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {} + + async generateBlockInput(): Promise> { + const identifier = ["dQw4w9WgXcQ"]; + const autoplay = faker.datatype.boolean(); + + return { + autoplay, + loop: faker.datatype.boolean(), + showControls: !autoplay, + youtubeIdentifier: faker.helpers.arrayElement(identifier), + previewImage: await this.pixelImageBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts new file mode 100644 index 0000000000..7a880ddb87 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/anchor-block-fixture.service.ts @@ -0,0 +1,12 @@ +import { AnchorBlock, ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class AnchorBlockFixtureService { + async generateBlockInput(): Promise> { + return { + name: faker.word.words(3), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts new file mode 100644 index 0000000000..0309402a82 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-block-fixture.service.ts @@ -0,0 +1,18 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { CallToActionBlock, Variant as CallToActionVariant } from "@src/common/blocks/call-to-action.block"; + +import { TextLinkBlockFixtureService } from "./text-link-block-fixture.service"; + +@Injectable() +export class CallToActionBlockFixtureService { + constructor(private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService) {} + + async generateBlockInput(): Promise> { + return { + textLink: await this.textLinkBlockFixtureService.generateBlockInput(), + variant: faker.helpers.arrayElement(Object.values(CallToActionVariant)), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts new file mode 100644 index 0000000000..03295b8581 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/call-to-action-list-block.service.ts @@ -0,0 +1,28 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block"; + +import { CallToActionBlockFixtureService } from "./call-to-action-block-fixture.service"; + +@Injectable() +export class CallToActionListBlockFixtureService { + constructor(private readonly callToActionBlockFixtureService: CallToActionBlockFixtureService) {} + + async generateBlockInput(min = 2, max = 6): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.callToActionBlockFixtureService.generateBlockInput(), + }); + } + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts new file mode 100644 index 0000000000..74d0b07c7c --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/link-block-fixture.service.ts @@ -0,0 +1,24 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { LinkBlock } from "@src/common/blocks/link.block"; + +const urls = ["https://vivid-planet.com/", "https://github.com/", "https://gitlab.com", "https://stackoverflow.com/"]; + +@Injectable() +export class LinkBlockFixtureService { + async generateBlockInput(): Promise> { + return { + attachedBlocks: [ + { + type: "external", + props: { + targetUrl: faker.helpers.arrayElement(urls), + openInNewWindow: faker.datatype.boolean(), + }, + }, + ], + activeType: "external", + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts new file mode 100644 index 0000000000..c84ed0576c --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/link-list-block-fixture.service.ts @@ -0,0 +1,27 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { LinkListBlock } from "@src/common/blocks/link-list.block"; +import { TextLinkBlockFixtureService } from "@src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service"; + +@Injectable() +export class LinkListBlockFixtureService { + constructor(private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService) {} + + async generateBlockInput(min = 1, max = 4): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.textLinkBlockFixtureService.generateBlockInput(), + }); + } + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts new file mode 100644 index 0000000000..70735f7502 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/standalone-call-to-action-list-block-fixture.service.ts @@ -0,0 +1,21 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { + Alignment as StandaloneCallToActionListBlockAlignment, + StandaloneCallToActionListBlock, +} from "@src/common/blocks/standalone-call-to-action-list.block"; + +import { CallToActionListBlockFixtureService } from "./call-to-action-list-block.service"; + +@Injectable() +export class StandaloneCallToActionListBlockFixtureService { + constructor(private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService) {} + + async generateBlockInput(): Promise> { + return { + alignment: faker.helpers.arrayElement(Object.values(StandaloneCallToActionListBlockAlignment)), + callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts new file mode 100644 index 0000000000..c77a2d34da --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/navigation/text-link-block-fixture.service.ts @@ -0,0 +1,18 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { TextLinkBlock } from "@src/common/blocks/text-link.block"; + +import { LinkBlockFixtureService } from "./link-block-fixture.service"; + +@Injectable() +export class TextLinkBlockFixtureService { + constructor(private readonly linkBlockFixtureService: LinkBlockFixtureService) {} + + async generateBlockInput(): Promise> { + return { + link: await this.linkBlockFixtureService.generateBlockInput(), + text: faker.lorem.words({ min: 1, max: 3 }), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts new file mode 100644 index 0000000000..c8f3a330a2 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/stage/basic-stage-block-fixture.service.ts @@ -0,0 +1,30 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { Alignment as BasicStageBlockAlignment, BasicStageBlock } from "@src/documents/pages/blocks/basic-stage.block"; + +import { MediaBlockFixtureService } from "../media/media-block.fixture.service"; +import { CallToActionListBlockFixtureService } from "../navigation/call-to-action-list-block.service"; +import { HeadingBlockFixtureService } from "../text-and-content/heading-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; + +@Injectable() +export class BasicStageBlockFixtureService { + constructor( + private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService, + private readonly headingBlockFixtureService: HeadingBlockFixtureService, + private readonly mediaBlockFixtureService: MediaBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + return { + media: await this.mediaBlockFixtureService.generateBlockInput(), + heading: await this.headingBlockFixtureService.generateBlockInput(), + text: await this.richTextBlockFixtureService.generateBlockInput(), + overlay: faker.number.int({ min: 50, max: 90 }), + alignment: faker.helpers.arrayElement(Object.values(BasicStageBlockAlignment)), + callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts new file mode 100644 index 0000000000..4b6d31927f --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/teaser/billboard-teaser-block-fixture.service.ts @@ -0,0 +1,29 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { BillboardTeaserBlock } from "@src/documents/pages/blocks/billboard-teaser.block"; + +import { MediaBlockFixtureService } from "../media/media-block.fixture.service"; +import { CallToActionListBlockFixtureService } from "../navigation/call-to-action-list-block.service"; +import { HeadingBlockFixtureService } from "../text-and-content/heading-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; + +@Injectable() +export class BillboardTeaserBlockFixtureService { + constructor( + private readonly callToActionListBlockFixtureService: CallToActionListBlockFixtureService, + private readonly headingBlockFixtureService: HeadingBlockFixtureService, + private readonly mediaBlockFixtureService: MediaBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + return { + media: await this.mediaBlockFixtureService.generateBlockInput(), + heading: await this.headingBlockFixtureService.generateBlockInput(), + text: await this.richTextBlockFixtureService.generateBlockInput(), + overlay: faker.number.int({ min: 50, max: 90 }), + callToActionList: await this.callToActionListBlockFixtureService.generateBlockInput(), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts new file mode 100644 index 0000000000..d812fac6cc --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/teaser/teaser-block-fixture.service.ts @@ -0,0 +1,44 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { TeaserBlock } from "@src/documents/pages/blocks/teaser.block"; +import { TeaserItemBlock } from "@src/documents/pages/blocks/teaser-item.block"; + +import { MediaBlockFixtureService } from "../media/media-block.fixture.service"; +import { TextLinkBlockFixtureService } from "../navigation/text-link-block-fixture.service"; +import { RichTextBlockFixtureService } from "../text-and-content/rich-text-block-fixture.service"; + +@Injectable() +export class TeaserBlockFixtureService { + constructor( + private readonly mediaBlockFixtureService: MediaBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + private readonly textLinkBlockFixtureService: TextLinkBlockFixtureService, + ) {} + + async generateTeaserItemBlock(): Promise> { + return { + media: await this.mediaBlockFixtureService.generateBlockInput(), + title: faker.lorem.words({ min: 3, max: 9 }), + description: await this.richTextBlockFixtureService.generateBlockInput(), + link: await this.textLinkBlockFixtureService.generateBlockInput(), + }; + } + + async generateBlockInput(min = 2, max = 6): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.generateTeaserItemBlock(), + }); + } + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts new file mode 100644 index 0000000000..b37bc993f1 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/heading-block-fixture.service.ts @@ -0,0 +1,41 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { HeadingBlock, HeadlineTag } from "@src/common/blocks/heading.block"; + +import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service"; + +@Injectable() +export class HeadingBlockFixtureService { + constructor(private readonly richTextBlockFixtureService: RichTextBlockFixtureService) {} + + async generateBlockInput(): Promise> { + const possibleTypes = ["h1", "h2", "h3", "h4", "subHeadlineMedium", "subHeadlineSmall"]; + + const eyebrowBlock = { + key: faker.string.uuid(), + text: faker.lorem.words({ min: 3, max: 9 }), + type: faker.helpers.arrayElement(possibleTypes), + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }; + + const headingBlock = { + key: faker.string.uuid(), + text: faker.lorem.words({ min: 3, max: 9 }), + type: faker.helpers.arrayElement(possibleTypes), + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }; + + return { + eyebrow: await this.richTextBlockFixtureService.generateBlockInput(1, [eyebrowBlock]), + headline: await this.richTextBlockFixtureService.generateBlockInput(1, [headingBlock]), + htmlTag: faker.helpers.arrayElement(Object.values(HeadlineTag)), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts new file mode 100644 index 0000000000..0342f08a64 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/key-facts-block-fixture.service.ts @@ -0,0 +1,42 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { KeyFactsBlock } from "@src/documents/pages/blocks/key-facts.block"; +import { KeyFactsItemBlock } from "@src/documents/pages/blocks/key-facts-item.block"; + +import { SvgImageBlockFixtureService } from "../media/svg-image-block-fixture.service"; +import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service"; + +@Injectable() +export class KeyFactsBlockFixtureService { + constructor( + private readonly svgImageBlockFixtureService: SvgImageBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + ) {} + + async generateKeyFactsItemBlock(): Promise> { + return { + icon: await this.svgImageBlockFixtureService.generateBlockInput(), + fact: faker.lorem.sentence(), + label: faker.lorem.sentence(), + description: await this.richTextBlockFixtureService.generateBlockInput(), + }; + } + + async generateBlockInput(min = 2, max = 6): Promise> { + const blockAmount = faker.number.int({ min, max }); + const blocks = []; + + for (let i = 0; i < blockAmount; i++) { + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.generateKeyFactsItemBlock(), + }); + } + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts new file mode 100644 index 0000000000..8f1819b3c1 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/rich-text-block-fixture.service.ts @@ -0,0 +1,37 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; + +@Injectable() +export class RichTextBlockFixtureService { + async generateBlockInput( + lineCount = 3, + blocks?: ExtractBlockInputFactoryProps["draftContent"]["blocks"], + ): Promise> { + const possibleTypes = ["textMedium", "textSmall", "textXSmall"]; + + const defaultBlocks: ExtractBlockInputFactoryProps["draftContent"]["blocks"] = []; + + if (!blocks) { + for (let i = 0; i < faker.number.int({ min: 1, max: 3 }); i++) { + defaultBlocks.push({ + key: faker.string.uuid(), + text: faker.lorem.paragraph(), + type: faker.helpers.arrayElement(possibleTypes), + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }); + } + } + + return { + draftContent: { + blocks: faker.helpers.arrayElements(blocks ?? defaultBlocks, lineCount), + entityMap: {}, + }, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts new file mode 100644 index 0000000000..90fd8dbdf0 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/standalone-heading-block-fixture.service.ts @@ -0,0 +1,18 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { StandaloneHeadingBlock, TextAlignment } from "@src/common/blocks/standalone-heading.block"; + +import { HeadingBlockFixtureService } from "./heading-block-fixture.service"; + +@Injectable() +export class StandaloneHeadingBlockFixtureService { + constructor(private readonly headingBlockFixtureService: HeadingBlockFixtureService) {} + + async generateBlockInput(): Promise> { + return { + heading: await this.headingBlockFixtureService.generateBlockInput(), + textAlignment: faker.helpers.arrayElement(Object.values(TextAlignment)), + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts new file mode 100644 index 0000000000..58d2c0a255 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/blocks/text-and-content/text-image-block-fixture.service.ts @@ -0,0 +1,24 @@ +import { ExtractBlockInputFactoryProps, ImagePosition } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { TextImageBlock } from "@src/common/blocks/text-image.block"; + +import { DamImageBlockFixtureService } from "../media/dam-image-block-fixture.service"; +import { RichTextBlockFixtureService } from "./rich-text-block-fixture.service"; + +@Injectable() +export class TextImageBlockFixtureService { + constructor( + private readonly damImageBlockFixtureService: DamImageBlockFixtureService, + private readonly richTextBlockFixtureService: RichTextBlockFixtureService, + ) {} + + async generateBlockInput(): Promise> { + return { + image: await this.damImageBlockFixtureService.generateBlockInput(), + text: await this.richTextBlockFixtureService.generateBlockInput(), + imagePosition: faker.helpers.arrayElement(Object.values(ImagePosition)), + imageAspectRatio: "16:9", + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/document-generator.service.ts b/demo/api/src/db/fixtures/generators/document-generator.service.ts new file mode 100644 index 0000000000..ecfb6f9b3b --- /dev/null +++ b/demo/api/src/db/fixtures/generators/document-generator.service.ts @@ -0,0 +1,76 @@ +import { PageTreeNodeInterface, PageTreeNodeVisibility, PageTreeService } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { InjectRepository } from "@mikro-orm/nestjs"; +import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; +import { Injectable } from "@nestjs/common"; +import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; +import { SeoBlock } from "@src/documents/pages/blocks/seo.block"; +import { StageBlock } from "@src/documents/pages/blocks/stage.block"; +import { Page } from "@src/documents/pages/entities/page.entity"; +import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope"; +import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category"; +import { UserGroup } from "@src/user-groups/user-group"; +import slugify from "slugify"; + +import { BlockCategory, PageContentBlockFixtureService } from "./page-content-block-fixture.service"; +import { SeoBlockFixtureService } from "./seo-block-fixture.service"; +import { StageBlockFixtureService } from "./stage-block-fixture.service"; + +interface GeneratePageInput { + name: string; + scope: PageTreeNodeScope; + parentId?: string; + category?: PageTreeNodeCategory; + blockCategory?: BlockCategory; +} + +@Injectable() +export class DocumentGeneratorService { + constructor( + private readonly pageTreeService: PageTreeService, + @InjectRepository(Page) private readonly pagesRepository: EntityRepository, + private readonly pageContentBlockFixtureService: PageContentBlockFixtureService, + private readonly stageBlockFixtureService: StageBlockFixtureService, + private readonly seoBlockFixtureService: SeoBlockFixtureService, + private readonly entityManager: EntityManager, + ) {} + + async generatePage({ + name, + scope, + parentId, + category = PageTreeNodeCategory.MainNavigation, + blockCategory, + }: GeneratePageInput): Promise { + const id = faker.string.uuid(); + const slug = slugify(name.toLowerCase(), { remove: /[*+~.()/'"!:@]/g }); + + const node = await this.pageTreeService.createNode( + { + name, + slug, + attachedDocument: { + id, + type: "Page", + }, + parentId, + // @ts-expect-error Typing of PageTreeService is wrong https://github.com/vivid-planet/comet/pull/1515#issue-2042001589 + userGroup: UserGroup.All, + }, + category, + scope, + ); + await this.pageTreeService.updateNodeVisibility(node.id, PageTreeNodeVisibility.Published); + + await this.entityManager.persistAndFlush( + this.pagesRepository.create({ + id, + content: PageContentBlock.blockDataFactory(await this.pageContentBlockFixtureService.generateBlockInput(blockCategory)), + seo: SeoBlock.blockDataFactory(await this.seoBlockFixtureService.generateBlockInput()), + stage: StageBlock.blockDataFactory(await this.stageBlockFixtureService.generateBlockInput()), + }), + ); + + return node; + } +} diff --git a/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts b/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts index b4ff4201aa..5ad829ad3b 100644 --- a/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts +++ b/demo/api/src/db/fixtures/generators/file-uploads-fixture.service.ts @@ -7,13 +7,11 @@ export class FileUploadsFixtureService { constructor(private readonly fileUploadsService: FileUploadsService) {} async generateFileUploads(): Promise { - console.log("Generating file uploads..."); - const images = ["01.jpg", "02.jpg", "03.jpg", "04.jpg", "05.jpg"]; const fileUploads: FileUpload[] = []; for (const image of images) { - const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/generators/images/${image}`)); + const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/assets/images/${image}`)); fileUploads.push(await this.fileUploadsService.upload(file)); } diff --git a/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts b/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts index 464dad8048..89f8852382 100644 --- a/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts +++ b/demo/api/src/db/fixtures/generators/image-file-fixture.service.ts @@ -13,7 +13,7 @@ export class ImageFileFixtureService { const images = []; for (let index = 0; index < IMAGE_FILE_PATHS.length; index++) { console.log(`Downloading ${IMAGE_FILE_PATHS[index]}.`); - const image_path = path.resolve(`./src/db/fixtures/generators/images/${IMAGE_FILE_PATHS[index]}.png`); + const image_path = path.resolve(`./src/db/fixtures/assets/images/${IMAGE_FILE_PATHS[index]}.png`); const downloadedImage = await createFileUploadInputFromUrl(image_path); console.log(`Downloading ${IMAGE_FILE_PATHS[index]} done.`); diff --git a/demo/api/src/db/fixtures/generators/image-fixture.service.ts b/demo/api/src/db/fixtures/generators/image-fixture.service.ts new file mode 100644 index 0000000000..6d8d1e9f6e --- /dev/null +++ b/demo/api/src/db/fixtures/generators/image-fixture.service.ts @@ -0,0 +1,77 @@ +import { createFileUploadInputFromUrl, FileInterface, FilesService } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { InjectRepository } from "@mikro-orm/nestjs"; +import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; +import { Injectable } from "@nestjs/common"; +import { DamScope } from "@src/dam/dto/dam-scope"; +import { DamFile } from "@src/dam/entities/dam-file.entity"; +import * as fs from "fs/promises"; +import path from "path"; + +@Injectable() +export class ImageFixtureService { + private pixelImageFiles: FileInterface[] = []; + private svgImageFiles: FileInterface[] = []; + + constructor( + private readonly filesService: FilesService, + @InjectRepository(DamFile) readonly filesRepository: EntityRepository, + private readonly entityManager: EntityManager, + ) {} + + public getRandomPixelImage() { + const randomIndex = faker.number.int({ + min: 0, + max: this.pixelImageFiles.length - 1, + }); + + return this.pixelImageFiles[randomIndex]; + } + + public getRandomSvg() { + const randomIndex = faker.number.int({ + min: 0, + max: this.svgImageFiles.length - 1, + }); + + return this.svgImageFiles[randomIndex]; + } + + private async generatePixelImages(maximumImageCount: number, scope: DamScope): Promise { + const directory = "./src/db/fixtures/assets/images"; + const allImages = await fs.readdir(path.resolve(directory)); + const images = faker.helpers.arrayElements(allImages, maximumImageCount); + + for (const image of images) { + const file = await createFileUploadInputFromUrl(path.resolve(`${directory}/${image}`)); + const pixelImage = await this.filesService.upload(file, { scope }); + + this.pixelImageFiles.push(pixelImage); + } + + await this.entityManager.flush(); + } + + private async generateSvgImages(maximumImageCount: number, scope: DamScope): Promise { + const svgDirectoryPath = "./src/db/fixtures/assets/svg"; + const files = await fs.readdir(path.resolve(svgDirectoryPath)); + + let count = 0; + for (const svg of files) { + const file = await createFileUploadInputFromUrl(path.resolve(`${svgDirectoryPath}/${svg}`)); + file.mimetype = "image/svg+xml"; // mime type is undefined for svg files and wrongly typed in download function (a possible undefined type is typed as string) + this.svgImageFiles.push(await this.filesService.upload(file, { scope })); + count++; + + if (count === maximumImageCount) break; + } + } + + public async generateImages(maximumImageCount: number, scope: DamScope): Promise { + console.log("Generate Pixel Images ..."); + await this.generatePixelImages(maximumImageCount, scope); + + console.log("Generate SVG Images ..."); + await this.generateSvgImages(maximumImageCount, scope); + } +} diff --git a/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts new file mode 100644 index 0000000000..8cd27f2bc4 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/page-content-block-fixture.service.ts @@ -0,0 +1,96 @@ +import { ExtractBlockInput, ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; + +import { BlockFixture } from "./blocks/block-fixture"; +import { AccordionBlockFixtureService } from "./blocks/layout/accordion-block-fixture.service"; +import { ColumnsBlockFixtureService } from "./blocks/layout/columns-block-fixture.service"; +import { ContentGroupBlockFixtureService } from "./blocks/layout/content-group-block-fixture.service"; +import { LayoutBlockFixtureService } from "./blocks/layout/layout-block-fixture.service"; +import { SpaceBlockFixtureService } from "./blocks/layout/space-block-fixture.service"; +import { DamImageBlockFixtureService } from "./blocks/media/dam-image-block-fixture.service"; +import { FullWidthImageBlockFixtureService } from "./blocks/media/full-width-image-block-fixture.service"; +import { MediaGalleryBlockFixtureService } from "./blocks/media/media-gallery-block-fixture.service"; +import { StandaloneMediaBlockFixtureService } from "./blocks/media/standalone-media-block-fixture.service"; +import { AnchorBlockFixtureService } from "./blocks/navigation/anchor-block-fixture.service"; +import { LinkListBlockFixtureService } from "./blocks/navigation/link-list-block-fixture.service"; +import { StandaloneCallToActionListBlockFixtureService } from "./blocks/navigation/standalone-call-to-action-list-block-fixture.service"; +import { BillboardTeaserBlockFixtureService } from "./blocks/teaser/billboard-teaser-block-fixture.service"; +import { TeaserBlockFixtureService } from "./blocks/teaser/teaser-block-fixture.service"; +import { KeyFactsBlockFixtureService } from "./blocks/text-and-content/key-facts-block-fixture.service"; +import { RichTextBlockFixtureService } from "./blocks/text-and-content/rich-text-block-fixture.service"; +import { StandaloneHeadingBlockFixtureService } from "./blocks/text-and-content/standalone-heading-block-fixture.service"; +import { TextImageBlockFixtureService } from "./blocks/text-and-content/text-image-block-fixture.service"; + +export type BlockCategory = "layout" | "media" | "navigation" | "teaser" | "textAndContent"; + +@Injectable() +export class PageContentBlockFixtureService { + constructor( + private readonly accordionBlockFixtureService: AccordionBlockFixtureService, + private readonly anchorBlockFixtureService: AnchorBlockFixtureService, + private readonly billboardTeaserBlockFixtureService: BillboardTeaserBlockFixtureService, + private readonly callToActionListBlockFixtureService: StandaloneCallToActionListBlockFixtureService, + private readonly columnsBlockFixtureService: ColumnsBlockFixtureService, + private readonly contentGroupBlockFixtureService: ContentGroupBlockFixtureService, + private readonly fullWidthImageBlockFixtureService: FullWidthImageBlockFixtureService, + private readonly headingBlockFixtureService: StandaloneHeadingBlockFixtureService, + private readonly imageBlockFixtureService: DamImageBlockFixtureService, + private readonly keyFactsBlockFixtureService: KeyFactsBlockFixtureService, + private readonly layoutBlockFixtureService: LayoutBlockFixtureService, + private readonly linkListBlockFixtureService: LinkListBlockFixtureService, + private readonly mediaGalleryBlockFixtureService: MediaGalleryBlockFixtureService, + private readonly richtextBlockFixtureService: RichTextBlockFixtureService, + private readonly spaceBlockFixtureService: SpaceBlockFixtureService, + private readonly mediaBlockFixtureService: StandaloneMediaBlockFixtureService, + private readonly teaserBlockFixtureService: TeaserBlockFixtureService, + private readonly textImageBlockFixtureService: TextImageBlockFixtureService, + ) {} + + async generateBlockInput(blockCategory?: BlockCategory): Promise> { + const blocks: ExtractBlockInputFactoryProps["blocks"] = []; + + type SupportedBlocks = (typeof blocks)[number]["type"]; + + // TODO add fixtures for newsDetail and newsList + const fixtures: Record, [BlockCategory, BlockFixture]> = { + accordion: ["layout", this.accordionBlockFixtureService], + columns: ["layout", this.columnsBlockFixtureService], + contentGroup: ["layout", this.contentGroupBlockFixtureService], + layout: ["layout", this.layoutBlockFixtureService], + space: ["layout", this.spaceBlockFixtureService], + fullWidthImage: ["media", this.fullWidthImageBlockFixtureService], + image: ["media", this.imageBlockFixtureService], + media: ["media", this.mediaBlockFixtureService], + mediaGallery: ["media", this.mediaGalleryBlockFixtureService], + anchor: ["navigation", this.anchorBlockFixtureService], + callToActionList: ["navigation", this.callToActionListBlockFixtureService], + billboardTeaser: ["teaser", this.billboardTeaserBlockFixtureService], + teaser: ["teaser", this.teaserBlockFixtureService], + heading: ["textAndContent", this.headingBlockFixtureService], + keyFacts: ["textAndContent", this.keyFactsBlockFixtureService], + richtext: ["textAndContent", this.richtextBlockFixtureService], + textImage: ["textAndContent", this.textImageBlockFixtureService], + }; + + const supportedBlocksFixtureGenerators = Object.entries(fixtures) + .filter(([, [category]]) => (blockCategory ? category === blockCategory : true)) + .map<[string, BlockFixture]>(([type, [, generator]]) => [type, generator]); + + for (const [type, generator] of supportedBlocksFixtureGenerators) { + const props = await generator.generateBlockInput(); + + blocks.push({ + key: faker.string.uuid(), + visible: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + type: type as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + props: props as any, + }); + } + + return PageContentBlock.blockInputFactory({ blocks }); + } +} diff --git a/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts b/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts index 7e053ca2f2..e5822d2fdb 100644 --- a/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts +++ b/demo/api/src/db/fixtures/generators/redirects-fixture.service.ts @@ -11,8 +11,6 @@ export class RedirectsFixtureService { ) {} async generateRedirects(): Promise { - console.log("Generating redirects..."); - for (let i = 0; i < 7000; i++) { this.repository.create({ generationType: RedirectGenerationType.manual, diff --git a/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts new file mode 100644 index 0000000000..a4980fabd0 --- /dev/null +++ b/demo/api/src/db/fixtures/generators/seo-block-fixture.service.ts @@ -0,0 +1,31 @@ +import { ExtractBlockInputFactoryProps, SitemapPageChangeFrequency, SitemapPagePriority } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { SeoBlock } from "@src/documents/pages/blocks/seo.block"; + +import { PixelImageBlockFixtureService } from "./blocks/media/pixel-image-block-fixture.service"; + +@Injectable() +export class SeoBlockFixtureService { + constructor(private readonly pixelImageBlockFixtureService: PixelImageBlockFixtureService) {} + + async generateBlockInput(): Promise> { + const alternativeLinks = []; + const codes = ["en_US", "en_GB", "es_ES", "fr_FR", "ja_JP", "it_IT"]; + for (let i = 0; i < faker.number.int({ min: 0, max: 5 }); i++) { + alternativeLinks.push({ code: codes[i], url: faker.internet.url() }); + } + + return { + htmlTitle: faker.word.words(2), + metaDescription: faker.word.words(20), + openGraphTitle: faker.word.words(2), + openGraphDescription: faker.word.words(20), + openGraphImage: { block: await this.pixelImageBlockFixtureService.generateBlockInput(), visible: faker.datatype.boolean() }, + noIndex: faker.datatype.boolean(), + priority: faker.helpers.arrayElement(Object.values(SitemapPagePriority)), + changeFrequency: faker.helpers.arrayElement(Object.values(SitemapPageChangeFrequency)), + alternativeLinks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts b/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts new file mode 100644 index 0000000000..deab1c911b --- /dev/null +++ b/demo/api/src/db/fixtures/generators/stage-block-fixture.service.ts @@ -0,0 +1,25 @@ +import { ExtractBlockInputFactoryProps } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { Injectable } from "@nestjs/common"; +import { StageBlock } from "@src/documents/pages/blocks/stage.block"; + +import { BasicStageBlockFixtureService } from "./blocks/stage/basic-stage-block-fixture.service"; + +@Injectable() +export class StageBlockFixtureService { + constructor(private readonly basicStageBlockFixtureService: BasicStageBlockFixtureService) {} + + async generateBlockInput(): Promise> { + const blocks: ExtractBlockInputFactoryProps["blocks"] = []; + + blocks.push({ + key: faker.string.uuid(), + visible: true, + props: await this.basicStageBlockFixtureService.generateBlockInput(), + }); + + return { + blocks: blocks, + }; + } +} diff --git a/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts b/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts index 26e368b43e..4bbdbea7d6 100644 --- a/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts +++ b/demo/api/src/db/fixtures/generators/svg-image-file-fixture.service.ts @@ -8,7 +8,7 @@ export class SvgImageFileFixtureService { constructor(private readonly filesService: FilesService) {} async generateImage(scope: DamScope): Promise { - const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/generators/images/comet-logo-claim.svg`)); + const file = await createFileUploadInputFromUrl(path.resolve(`./src/db/fixtures/assets/svg/comet-logo-claim.svg`)); // Convert to what the browser would send file.mimetype = "image/svg+xml"; file.originalname = "comet-logo-claim.svg"; diff --git a/demo/api/src/db/fixtures/generators/video-fixture.service.ts b/demo/api/src/db/fixtures/generators/video-fixture.service.ts new file mode 100644 index 0000000000..cb6379a48e --- /dev/null +++ b/demo/api/src/db/fixtures/generators/video-fixture.service.ts @@ -0,0 +1,41 @@ +import { createFileUploadInputFromUrl, FileInterface, FilesService } from "@comet/cms-api"; +import { faker } from "@faker-js/faker"; +import { InjectRepository } from "@mikro-orm/nestjs"; +import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; +import { Injectable } from "@nestjs/common"; +import { DamScope } from "@src/dam/dto/dam-scope"; +import { DamFile } from "@src/dam/entities/dam-file.entity"; +import * as fs from "fs/promises"; +import path from "path"; + +@Injectable() +export class VideoFixtureService { + private videoFiles: FileInterface[] = []; + + constructor( + private readonly filesService: FilesService, + @InjectRepository(DamFile) readonly filesRepository: EntityRepository, + private readonly entityManager: EntityManager, + ) {} + + public getRandomVideo() { + const randomIndex = faker.number.int({ + min: 0, + max: this.videoFiles.length - 1, + }); + + return this.videoFiles[randomIndex]; + } + + public async generateVideos(scope: DamScope): Promise { + const videoDirectoryPath = "./src/db/fixtures/assets/videos"; + const files = await fs.readdir(path.resolve(videoDirectoryPath)); + + for (const video of files) { + const file = await createFileUploadInputFromUrl(path.resolve(`${videoDirectoryPath}/${video}`)); + this.videoFiles.push(await this.filesService.upload(file, { scope })); + } + + await this.entityManager.flush(); + } +} diff --git a/demo/api/src/documents/pages/blocks/basic-stage.block.ts b/demo/api/src/documents/pages/blocks/basic-stage.block.ts index fa798a1b9a..98c01e06b5 100644 --- a/demo/api/src/documents/pages/blocks/basic-stage.block.ts +++ b/demo/api/src/documents/pages/blocks/basic-stage.block.ts @@ -15,7 +15,7 @@ import { MediaBlock } from "@src/common/blocks/media.block"; import { RichTextBlock } from "@src/common/blocks/rich-text.block"; import { IsEnum, IsInt, Max, Min } from "class-validator"; -enum Alignment { +export enum Alignment { left = "left", center = "center", } diff --git a/demo/api/src/documents/pages/blocks/columns.block.ts b/demo/api/src/documents/pages/blocks/columns.block.ts index 91e0e94e66..4043a4767c 100644 --- a/demo/api/src/documents/pages/blocks/columns.block.ts +++ b/demo/api/src/documents/pages/blocks/columns.block.ts @@ -7,7 +7,7 @@ import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-c import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block"; import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block"; -const ColumnsContentBlock = createBlocksBlock( +export const ColumnsContentBlock = createBlocksBlock( { supportedBlocks: { accordion: AccordionBlock, @@ -20,9 +20,7 @@ const ColumnsContentBlock = createBlocksBlock( mediaGallery: MediaGalleryBlock, }, }, - { - name: "ColumnsContent", - }, + { name: "ColumnsContent" }, ); export const ColumnsBlock = ColumnsBlockFactory.create( diff --git a/demo/api/src/documents/pages/blocks/content-group.block.ts b/demo/api/src/documents/pages/blocks/content-group.block.ts index f7ba95590e..cd334fb1b5 100644 --- a/demo/api/src/documents/pages/blocks/content-group.block.ts +++ b/demo/api/src/documents/pages/blocks/content-group.block.ts @@ -23,26 +23,26 @@ import { KeyFactsBlock } from "@src/documents/pages/blocks/key-facts.block"; import { TeaserBlock } from "@src/documents/pages/blocks/teaser.block"; import { IsEnum } from "class-validator"; -const ContentBlock = createBlocksBlock( +export const ContentBlock = createBlocksBlock( { supportedBlocks: { accordion: AccordionBlock, anchor: AnchorBlock, - space: SpaceBlock, - teaser: TeaserBlock, - richtext: RichTextBlock, - heading: StandaloneHeadingBlock, - columns: ColumnsBlock, callToActionList: StandaloneCallToActionListBlock, + columns: ColumnsBlock, + heading: StandaloneHeadingBlock, keyFacts: KeyFactsBlock, media: StandaloneMediaBlock, mediaGallery: MediaGalleryBlock, + richtext: RichTextBlock, + space: SpaceBlock, + teaser: TeaserBlock, }, }, { name: "ContentGroupContent" }, ); -enum BackgroundColor { +export enum BackgroundColor { default = "default", lightGray = "lightGray", darkGray = "darkGray", diff --git a/demo/api/src/documents/pages/blocks/layout.block.ts b/demo/api/src/documents/pages/blocks/layout.block.ts index 82fb766e74..6a7c249f07 100644 --- a/demo/api/src/documents/pages/blocks/layout.block.ts +++ b/demo/api/src/documents/pages/blocks/layout.block.ts @@ -13,7 +13,7 @@ import { MediaBlock } from "@src/common/blocks/media.block"; import { RichTextBlock } from "@src/common/blocks/rich-text.block"; import { IsEnum } from "class-validator"; -enum LayoutBlockLayout { +export enum LayoutBlockLayout { layout1 = "layout1", layout2 = "layout2", layout3 = "layout3", diff --git a/demo/build-and-run-site.sh b/demo/build-and-run-site.sh new file mode 100755 index 0000000000..c0465694f1 --- /dev/null +++ b/demo/build-and-run-site.sh @@ -0,0 +1,24 @@ +# Execute this script via `npm run build-and-run-site` +# +# This script builds the site like in the CI and starts it. +# +# Reasons why you want to do this: +# - Check if it builds without warnings +# - Check if there are suggestions from next build +# - Check if it behaves in the same way like the dev-server +# - Check caching behaviour, e.g. Cache-Control header (which is always no-cache in dev-server) + +#!/usr/bin/env bash + +echo "[1/2] Build site..." +cd site +rm -f .env .env.local .env.site-configs +rm -rf .next +NODE_ENV=production npm run build +ln -sf ../../.env ./ +ln -sf ../../.env.local ./ +ln -sf ../.env.site-configs ./ +echo "" + +echo "[2/2] Start site..." +npx dotenv -e .env.secrets -e .env.site-configs -- npm run serve diff --git a/demo/site/next.config.mjs b/demo/site/next.config.mjs index f6ff9807c5..d322f3d79d 100644 --- a/demo/site/next.config.mjs +++ b/demo/site/next.config.mjs @@ -29,6 +29,17 @@ const nextConfig = { }, cacheHandler: process.env.REDIS_ENABLED === "true" ? import.meta.resolve("./dist/cache-handler.js").replace("file://", "") : undefined, cacheMaxMemorySize: process.env.REDIS_ENABLED === "true" ? 0 : undefined, // disable default in-memory caching + rewrites: () => { + return { + afterFiles: [ + { + // Show a 404 instead of trying to render page for paths starting with /_next/ or /assets/ as they don't get rewritten in DomainRewriteMiddleware and cause errors in ...path page + source: "/:prefix(_next|assets)/:path*", + destination: "/404", + }, + ], + }; + }, }; export default withBundleAnalyzer(nextConfig); diff --git a/demo/site/server.ts b/demo/site/server.ts index 19deeb073e..cb68b9e98e 100644 --- a/demo/site/server.ts +++ b/demo/site/server.ts @@ -53,6 +53,16 @@ app.prepare().then(() => { }; } + const originalWriteHead = res.writeHead; + res.writeHead = function (statusCode: number, ...args: unknown[]) { + // since writeHead is a callback function, it's called after handle() -> we get the actual response statusCode + if (statusCode >= 400) { + // prevent caching of error responses + res.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate"); + } + return originalWriteHead.apply(this, [statusCode, ...args]); + }; + await handle(req, res, parsedUrl); } catch (err) { console.error("Error occurred handling", req.url, err); diff --git a/demo/site/src/app/not-found.tsx b/demo/site/src/app/not-found.tsx new file mode 100644 index 0000000000..62c3d0b989 --- /dev/null +++ b/demo/site/src/app/not-found.tsx @@ -0,0 +1,12 @@ +import Link from "next/link"; + +export default function NotFound404() { + return ( + + +

Page not found.

+ Return Home + + + ); +} diff --git a/demo/site/src/middleware/redirectToMainHost.ts b/demo/site/src/middleware/redirectToMainHost.ts index 98b8ddda50..d81e51b8c6 100644 --- a/demo/site/src/middleware/redirectToMainHost.ts +++ b/demo/site/src/middleware/redirectToMainHost.ts @@ -1,8 +1,25 @@ +import type { PublicSiteConfig } from "@src/site-configs"; import { getHostByHeaders, getSiteConfigForHost, getSiteConfigs } from "@src/util/siteConfig"; import { type NextRequest, NextResponse } from "next/server"; import { type CustomMiddleware } from "./chain"; +const normalizeDomain = (host: string) => (host.startsWith("www.") ? host.substring(4) : host); + +const matchesHostWithAdditionalDomain = (siteConfig: PublicSiteConfig, host: string) => { + if (normalizeDomain(siteConfig.domains.main) === normalizeDomain(host)) return true; // non-www redirect + if (siteConfig.domains.additional?.map(normalizeDomain).includes(normalizeDomain(host))) return true; + return false; +}; + +const matchesHostWithPattern = (siteConfig: PublicSiteConfig, host: string) => { + if (!siteConfig.domains.pattern) return false; + return new RegExp(siteConfig.domains.pattern).test(host); +}; + +/** + * When http host isn't siteConfig.domains.main (instead .pattern or .additional match), redirect to main host. + */ export function withRedirectToMainHostMiddleware(middleware: CustomMiddleware) { return async (request: NextRequest) => { const headers = request.headers; @@ -11,16 +28,14 @@ export function withRedirectToMainHostMiddleware(middleware: CustomMiddleware) { if (!siteConfig) { // Redirect to Main Host - const redirectSiteConfig = getSiteConfigs().find( - (siteConfig) => - siteConfig.domains.additional?.includes(host) || - (siteConfig.domains.pattern && host.match(new RegExp(siteConfig.domains.pattern))), - ); + const redirectSiteConfig = + getSiteConfigs().find((siteConfig) => matchesHostWithAdditionalDomain(siteConfig, host)) || + getSiteConfigs().find((siteConfig) => matchesHostWithPattern(siteConfig, host)); if (redirectSiteConfig) { - return NextResponse.redirect(redirectSiteConfig.url); + return NextResponse.redirect(redirectSiteConfig.url + request.nextUrl.pathname + request.nextUrl.search, { status: 301 }); } - return NextResponse.next({ status: 404 }); + return NextResponse.json({ error: `Cannot resolve domain: ${host}` }, { status: 404 }); } return middleware(request); }; diff --git a/docs/docs/6-deployment/index.md b/docs/docs/6-deployment/index.md index 75ef386725..a169cf0116 100644 --- a/docs/docs/6-deployment/index.md +++ b/docs/docs/6-deployment/index.md @@ -42,4 +42,4 @@ Serverless container platforms are a good option for those who want to deploy Co #### Docker Compose -For those with budget constraints, Docker Compose can be a viable option for deploying Comet applications. +For those with budget constraints, [Docker Compose](https://docs.docker.com/compose/) can be a viable option for deploying Comet applications. An example deployment can be found [here](https://github.com/vivid-planet/comet-starter/tree/main/.docker-compose). The deployment only requires Docker to be installed on a server and leverages the power of [Traefik](https://doc.traefik.io/traefik/) to manage the ingress traffic (including SSL certificates). diff --git a/docs/docs/7-migration-guide/migration-from-v6-to-v7.md b/docs/docs/7-migration-guide/migration-from-v6-to-v7.md index 5a38010156..0470d5504d 100644 --- a/docs/docs/7-migration-guide/migration-from-v6-to-v7.md +++ b/docs/docs/7-migration-guide/migration-from-v6-to-v7.md @@ -236,7 +236,7 @@ DamModule.register({ }) ``` -#### How to migrate (only required if CDN is used): +#### How to migrate (only required if CDN is used with `DAM_CDN_ORIGIN_HEADER`): Remove the following env vars from the API @@ -288,26 +288,7 @@ If you want to enable the origin check: + } ``` -3. Adjust `site/server.js` - -```diff -// site/server.js - -- const cdnEnabled = process.env.CDN_ENABLED === "true"; -- const disableCdnOriginHeaderCheck = process.env.DISABLE_CDN_ORIGIN_HEADER_CHECK === "true"; -- const cdnOriginHeader = process.env.CDN_ORIGIN_HEADER; -+ const cdnOriginCheckSecret = process.env.CDN_ORIGIN_CHECK_SECRET; - -// ... - -- if (cdnEnabled && !disableCdnOriginHeaderCheck) { -- const incomingCdnOriginHeader = req.headers["x-cdn-origin-check"]; -- if (cdnOriginHeader !== incomingCdnOriginHeader) { -+ if (cdnOriginCheckSecret) { -+ if (req.headers["x-cdn-origin-check"] !== cdnOriginCheckSecret) { -``` - -4. DNS changes might be required. `api.example.com` should point to CDN, CDN should point to internal API domain +3. DNS changes might be required. `api.example.com` should point to CDN, CDN should point to internal API domain ### API Generator: Remove support for `visible` boolean, use `status` enum instead @@ -1080,6 +1061,25 @@ const nextConfig = { module.exports = withBundleAnalyzer(nextConfig); ``` +### Adjust CDN config in `site/server.js` + +```diff +// site/server.js + +- const cdnEnabled = process.env.CDN_ENABLED === "true"; +- const disableCdnOriginHeaderCheck = process.env.DISABLE_CDN_ORIGIN_HEADER_CHECK === "true"; +- const cdnOriginHeader = process.env.CDN_ORIGIN_HEADER; ++ const cdnOriginCheckSecret = process.env.CDN_ORIGIN_CHECK_SECRET; + +// ... + +- if (cdnEnabled && !disableCdnOriginHeaderCheck) { +- const incomingCdnOriginHeader = req.headers["x-cdn-origin-check"]; +- if (cdnOriginHeader !== incomingCdnOriginHeader) { ++ if (cdnOriginCheckSecret) { ++ if (req.headers["x-cdn-origin-check"] !== cdnOriginCheckSecret) { +``` + ### Add a custom `InternalLinkBlock` The `InternalLinkBlock` provided by `@comet/cms-site` is deprecated. diff --git a/docs/lint-staged.config.mjs b/docs/lint-staged.config.mjs index 03addb55b0..3ad1e888c3 100644 --- a/docs/lint-staged.config.mjs +++ b/docs/lint-staged.config.mjs @@ -5,4 +5,5 @@ export default { "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", + "docs/**": "cspell", }; diff --git a/packages/admin/admin-babel-preset/CHANGELOG.md b/packages/admin/admin-babel-preset/CHANGELOG.md index 941ae17cc2..710eb4e624 100644 --- a/packages/admin/admin-babel-preset/CHANGELOG.md +++ b/packages/admin/admin-babel-preset/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/admin-babel-preset +## 7.15.0 + ## 7.14.0 ## 7.13.0 diff --git a/packages/admin/admin-babel-preset/package.json b/packages/admin/admin-babel-preset/package.json index d31e835a40..7a6d353db5 100644 --- a/packages/admin/admin-babel-preset/package.json +++ b/packages/admin/admin-babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-babel-preset", - "version": "7.14.0", + "version": "7.15.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", diff --git a/packages/admin/admin-color-picker/CHANGELOG.md b/packages/admin/admin-color-picker/CHANGELOG.md index 77caa63cce..c334602587 100644 --- a/packages/admin/admin-color-picker/CHANGELOG.md +++ b/packages/admin/admin-color-picker/CHANGELOG.md @@ -1,5 +1,17 @@ # @comet/admin-color-picker +## 7.15.0 + +### Patch Changes + +- Updated dependencies [a189d4ed9] +- Updated dependencies [faa54eb8e] +- Updated dependencies [7d8c36e6c] +- Updated dependencies [a189d4ed9] +- Updated dependencies [6827982fe] + - @comet/admin@7.15.0 + - @comet/admin-icons@7.15.0 + ## 7.14.0 ### Patch Changes diff --git a/packages/admin/admin-color-picker/package.json b/packages/admin/admin-color-picker/package.json index 541e8c25b4..4a7c387384 100644 --- a/packages/admin/admin-color-picker/package.json +++ b/packages/admin/admin-color-picker/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-color-picker", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/admin-color-picker", "type": "git", @@ -26,8 +26,8 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin": "workspace:^7.14.0", - "@comet/admin-icons": "workspace:^7.14.0", + "@comet/admin": "workspace:^7.15.0", + "@comet/admin-icons": "workspace:^7.15.0", "react-colorful": "^5.6.1", "tinycolor2": "^1.6.0", "use-debounce": "^10.0.4" @@ -35,8 +35,8 @@ "devDependencies": { "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@mui/material": "^6.4.5", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", diff --git a/packages/admin/admin-date-time/CHANGELOG.md b/packages/admin/admin-date-time/CHANGELOG.md index 51906184f1..59792d3a4e 100644 --- a/packages/admin/admin-date-time/CHANGELOG.md +++ b/packages/admin/admin-date-time/CHANGELOG.md @@ -1,5 +1,17 @@ # @comet/admin-date-time +## 7.15.0 + +### Patch Changes + +- Updated dependencies [a189d4ed9] +- Updated dependencies [faa54eb8e] +- Updated dependencies [7d8c36e6c] +- Updated dependencies [a189d4ed9] +- Updated dependencies [6827982fe] + - @comet/admin@7.15.0 + - @comet/admin-icons@7.15.0 + ## 7.14.0 ### Patch Changes diff --git a/packages/admin/admin-date-time/package.json b/packages/admin/admin-date-time/package.json index ffe49d9175..15429762f1 100644 --- a/packages/admin/admin-date-time/package.json +++ b/packages/admin/admin-date-time/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-date-time", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/admin-date-time", "type": "git", @@ -26,8 +26,8 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin": "workspace:^7.14.0", - "@comet/admin-icons": "workspace:^7.14.0", + "@comet/admin": "workspace:^7.15.0", + "@comet/admin-icons": "workspace:^7.15.0", "@mui/utils": "^6.4.3", "date-fns": "^4.1.0", "react-date-range": "^2.0.1" @@ -35,8 +35,8 @@ "devDependencies": { "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@mui/material": "^6.4.5", "@types/react": "^18.3.18", "@types/react-date-range": "^1.4.9", diff --git a/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts b/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts index f1edaca92c..66d447f28c 100644 --- a/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts +++ b/packages/admin/admin-generator/src/commands/generate/generateForm/generateFormLayout.ts @@ -70,7 +70,7 @@ export function generateFormLayout({ code = `
} ${ config.supportText diff --git a/packages/admin/admin-icons/CHANGELOG.md b/packages/admin/admin-icons/CHANGELOG.md index ec67f42bdf..07bd059b2f 100644 --- a/packages/admin/admin-icons/CHANGELOG.md +++ b/packages/admin/admin-icons/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/admin-icons +## 7.15.0 + ## 7.14.0 ## 7.13.0 diff --git a/packages/admin/admin-icons/package.json b/packages/admin/admin-icons/package.json index 147389879b..9d2b2e4725 100644 --- a/packages/admin/admin-icons/package.json +++ b/packages/admin/admin-icons/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-icons", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/admin-icons", "type": "git", @@ -29,8 +29,8 @@ "devDependencies": { "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@mui/material": "^6.4.5", "@types/cli-progress": "^3.11.6", "@types/node": "^22.13.5", diff --git a/packages/admin/admin-rte/CHANGELOG.md b/packages/admin/admin-rte/CHANGELOG.md index a13616081d..1f5fea192f 100644 --- a/packages/admin/admin-rte/CHANGELOG.md +++ b/packages/admin/admin-rte/CHANGELOG.md @@ -1,5 +1,17 @@ # @comet/admin-rte +## 7.15.0 + +### Patch Changes + +- Updated dependencies [a189d4ed9] +- Updated dependencies [faa54eb8e] +- Updated dependencies [7d8c36e6c] +- Updated dependencies [a189d4ed9] +- Updated dependencies [6827982fe] + - @comet/admin@7.15.0 + - @comet/admin-icons@7.15.0 + ## 7.14.0 ### Minor Changes diff --git a/packages/admin/admin-rte/package.json b/packages/admin/admin-rte/package.json index 545ef35bb9..073c6a632f 100644 --- a/packages/admin/admin-rte/package.json +++ b/packages/admin/admin-rte/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-rte", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/admin-rte", "type": "git", @@ -28,8 +28,8 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin": "workspace:^7.14.0", - "@comet/admin-icons": "workspace:^7.14.0", + "@comet/admin": "workspace:^7.15.0", + "@comet/admin-icons": "workspace:^7.15.0", "detect-browser": "^5.3.0", "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", @@ -39,8 +39,8 @@ "devDependencies": { "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@mui/material": "^6.4.5", "@types/draft-js": "^0.11.18", "@types/jest": "^29.5.14", diff --git a/packages/admin/admin/CHANGELOG.md b/packages/admin/admin/CHANGELOG.md index e6c34f9dce..43893bd94b 100644 --- a/packages/admin/admin/CHANGELOG.md +++ b/packages/admin/admin/CHANGELOG.md @@ -1,5 +1,56 @@ # @comet/admin +## 7.15.0 + +### Minor Changes + +- a189d4ed9: Support dynamic values for the `label` prop of `SwitchField` depending on its `checked` state + + ```tsx + (checked ? "On" : "Off")} /> + ``` + +- 7d8c36e6c: Add the `DataGridPanel` component to replace MUIs default `Panel` used by `DataGrid` to match the Comet DXP design + + It is recommended to add this component to your theme's `defaultProps` of `MuiDataGrid`. + + Example theme configuration for `admin/src/theme.ts`: + + ```ts + import { DataGridPanel } from "@comet/admin"; + import { createCometTheme } from "@comet/admin-theme"; + import type {} from "@mui/x-data-grid/themeAugmentation"; + + export const theme = createCometTheme({ + components: { + MuiDataGrid: { + defaultProps: { + components: { + Panel: DataGridPanel, + }, + }, + }, + }, + }); + ``` + +- a189d4ed9: Allow passing a `ReactNode` to `fieldLabel` of `CheckboxField` and `SwitchField` + + This enables using `FormattedMessage` for the label. + + ```tsx + } /> + } /> + ``` + +### Patch Changes + +- faa54eb8e: Fix display of warnings for forms that use both form-level and field-level validation +- 6827982fe: Preserve the default `Button` color when using the `sx` prop with the `textLight` or `textDark` variant +- Updated dependencies [7d8c36e6c] + - @comet/admin-theme@7.15.0 + - @comet/admin-icons@7.15.0 + ## 7.14.0 ### Minor Changes diff --git a/packages/admin/admin/package.json b/packages/admin/admin/package.json index 7446604699..0962bbf4e7 100644 --- a/packages/admin/admin/package.json +++ b/packages/admin/admin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/admin", "type": "git", @@ -28,7 +28,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin-icons": "workspace:^7.14.0", + "@comet/admin-icons": "workspace:^7.15.0", "@mui/utils": "^6.4.3", "exceljs": "^4.4.0", "file-saver": "^2.0.5", @@ -46,9 +46,9 @@ "@apollo/client": "^3.13.1", "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", - "@comet/eslint-plugin": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", + "@comet/eslint-plugin": "workspace:^7.15.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/material": "^6.4.5", diff --git a/packages/admin/admin/src/common/buttons/Button.tsx b/packages/admin/admin/src/common/buttons/Button.tsx index d3fadee9b6..cd46c287f4 100644 --- a/packages/admin/admin/src/common/buttons/Button.tsx +++ b/packages/admin/admin/src/common/buttons/Button.tsx @@ -71,6 +71,7 @@ const getMobileIconNode = ({ mobileIcon, startIcon, endIcon }: Pick(inProps: ButtonProps, ref: ForwardedRef) => { const { slotProps, + sx, variant = "primary", responsive, mobileIcon = "auto", @@ -97,6 +98,10 @@ export const Button = forwardRef((inProps: But const commonButtonProps = { ...variantToMuiProps[variant], + sx: { + ...variantToMuiProps[variant].sx, + ...sx, + }, ...restProps, ownerState, ...slotProps?.root, diff --git a/packages/admin/admin/src/dataGrid/DataGridPanel.tsx b/packages/admin/admin/src/dataGrid/DataGridPanel.tsx new file mode 100644 index 0000000000..8215b88e31 --- /dev/null +++ b/packages/admin/admin/src/dataGrid/DataGridPanel.tsx @@ -0,0 +1,408 @@ +import { Add, Check, Close, Reset } from "@comet/admin-icons"; +import { + type ComponentsOverrides, + css, + // eslint-disable-next-line no-restricted-imports + Dialog, + DialogContent, + Divider, + type Theme, + Typography, + useMediaQuery, + useTheme, + useThemeProps, +} from "@mui/material"; +import { + gridColumnVisibilityModelSelector, + gridFilterableColumnDefinitionsSelector, + type GridFilterItem, + gridFilterModelSelector, + GridPanel, + type GridPanelProps, + gridPreferencePanelStateSelector, + GridPreferencePanelsValue, + useGridApiContext, + useGridRootProps, + useGridSelector, +} from "@mui/x-data-grid"; +import { type DataGridProcessedProps } from "@mui/x-data-grid/internals"; +import { type ReactNode, useCallback, useMemo } from "react"; +import { FormattedMessage } from "react-intl"; + +import { Button } from "../common/buttons/Button"; +import { createComponentSlot } from "../helpers/createComponentSlot"; +import { type ThemedComponentBaseProps } from "../helpers/ThemedComponentBaseProps"; + +const panelTypeTitle: Record = { + [GridPreferencePanelsValue.filters]: , + [GridPreferencePanelsValue.columns]: , +}; + +export type DataGridPanelClassKey = + | "desktopGridPanel" + | "desktopPanelFooterDivider" + | "desktopPanelFooter" + | "desktopAddFilterButton" + | "mobileDialog" + | "mobileDialogHeader" + | "mobileDialogTitle" + | "mobileDialogCloseButton" + | "mobileDialogContent" + | "mobileDialogFooter" + | "mobileAddFilterButtonWrapper" + | "mobileAddFilterButton" + | "mobileDialogFooterActions" + | "resetFiltersButton" + | "resetColumnsButton" + | "applyButton"; + +export type DataGridPanelProps = Pick & + Omit< + ThemedComponentBaseProps<{ + desktopGridPanel: typeof GridPanel; + desktopPanelFooterDivider: typeof Divider; + desktopPanelFooter: "div"; + desktopAddFilterButton: typeof Button; + mobileDialog: typeof Dialog; + mobileDialogHeader: "div"; + mobileDialogTitle: typeof Typography; + mobileDialogCloseButton: typeof Button; + mobileDialogContent: typeof DialogContent; + mobileDialogFooter: "div"; + mobileAddFilterButtonWrapper: "div"; + mobileAddFilterButton: typeof Button; + mobileDialogFooterActions: "div"; + resetFiltersButton: typeof Button; + resetColumnsButton: typeof Button; + applyButton: typeof Button; + }>, + "sx" + > & { + iconMapping?: { + closeDialog?: ReactNode; + addFilter?: ReactNode; + resetFilters?: ReactNode; + resetColumns?: ReactNode; + apply?: ReactNode; + }; + }; + +const addFilterText = ; + +let lastAddedFilterItemId = 0; + +export const DataGridPanel = (inProps: DataGridPanelProps) => { + const { children, open, slotProps, iconMapping = {} } = useThemeProps({ props: inProps, name: "CometAdminDataGridPanel" }); + const apiRef = useGridApiContext(); + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const filterableColumns = useGridSelector(apiRef, gridFilterableColumnDefinitionsSelector); + const { openedPanelValue } = useGridSelector(apiRef, gridPreferencePanelStateSelector); + const initialColumnVisibilityModel = useMemo(() => gridColumnVisibilityModelSelector(apiRef), [apiRef]); + const rootProps = useGridRootProps(); + + const { + closeDialog: closeDialogIcon = , + addFilter: addFilterIcon = , + resetFilters: resetFiltersIcon = , + resetColumns: resetColumnsIcon = , + apply: applyIcon = , + } = iconMapping; + + const geNewFilterItem = useCallback(() => { + const firstColumnWithOperator = filterableColumns.find((colDef) => colDef.filterOperators?.length); + + if (!firstColumnWithOperator?.filterOperators?.length) { + return null; + } + + return { + field: firstColumnWithOperator.field, + operator: firstColumnWithOperator.filterOperators[0].value, + id: lastAddedFilterItemId++, + }; + }, [filterableColumns]); + + const filterItems = useMemo(() => { + if (filterModel.items.length) { + return filterModel.items; + } + + const newFilterItem = geNewFilterItem(); + + if (newFilterItem) { + return [newFilterItem]; + } + + return []; + }, [filterModel.items, geNewFilterItem]); + + const addNewFilter = useCallback(() => { + const newFilterItem = geNewFilterItem(); + + if (newFilterItem) { + apiRef.current.upsertFilterItems([...filterItems, newFilterItem]); + } + }, [apiRef, filterItems, geNewFilterItem]); + + const resetFilters = useCallback(() => { + apiRef.current.setFilterModel({ items: [] }); + }, [apiRef]); + + const resetColumns = useCallback(() => { + apiRef.current.setColumnVisibilityModel(initialColumnVisibilityModel); + }, [apiRef, initialColumnVisibilityModel]); + + const closeDialog = useCallback(() => { + if (openedPanelValue === GridPreferencePanelsValue.filters) { + apiRef.current.hideFilterPanel(); + } else { + apiRef.current.hidePreferences(); + } + }, [apiRef, openedPanelValue]); + + const theme = useTheme(); + const renderFullScreen = useMediaQuery(theme.breakpoints.down("md")); + + const resetFiltersButton = ( + + + + ); + + const resetColumnsButton = ( + + + + ); + + const applyButton = ( + + + + ); + + if (renderFullScreen) { + return ( + + + {!!openedPanelValue && panelTypeTitle[openedPanelValue]} + + {closeDialogIcon} + + + {children} + + {openedPanelValue === GridPreferencePanelsValue.filters && !rootProps.disableMultipleColumnsFiltering && ( + + + {addFilterText} + + + )} + + {openedPanelValue === GridPreferencePanelsValue.filters && ( + <> + {resetFiltersButton} + {applyButton} + + )} + {openedPanelValue === GridPreferencePanelsValue.columns && ( + <> + {resetColumnsButton} + {applyButton} + + )} + + + + ); + } + + return ( + + {children} + + + {openedPanelValue === GridPreferencePanelsValue.filters && ( + <> +
+ {!rootProps.disableMultipleColumnsFiltering && ( + + {addFilterText} + + )} +
+ {resetFiltersButton} + + )} + {openedPanelValue === GridPreferencePanelsValue.columns && ( + <> + {resetColumnsButton} + {applyButton} + + )} +
+
+ ); +}; + +const DesktopGridPanel = createComponentSlot(GridPanel)({ + componentName: "DataGridPanel", + slotName: "desktopGridPanel", +})(css` + .MuiDataGrid-panelHeader, + .MuiDataGrid-panelFooter { + // Hide MUIs header and footer so we can add our own with a better structure for styling + display: none; + } +`); + +const DesktopPanelFooterDivider = createComponentSlot(Divider)({ + componentName: "DataGridPanel", + slotName: "desktopPanelFooterDivider", +})(); + +const DesktopPanelFooter = createComponentSlot("div")({ + componentName: "DataGridPanel", + slotName: "desktopPanelFooter", +})( + ({ theme }) => css` + display: flex; + justify-content: space-between; + padding: ${theme.spacing(4)}; + `, +); + +const DesktopAddFilterButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "desktopAddFilterButton", +})(); + +const MobileDialog = createComponentSlot(Dialog)({ + componentName: "DataGridPanel", + slotName: "mobileDialog", +})(css` + .MuiDataGrid-panelContent { + max-height: none; + } + + .MuiDataGrid-panelHeader, + .MuiDataGrid-panelFooter { + // Hide MUIs header and footer so we can add our own with a better structure for styling + display: none; + } +`); + +const MobileDialogHeader = createComponentSlot("div")({ + componentName: "DataGridPanel", + slotName: "mobileDialogHeader", +})( + ({ theme }) => css` + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: ${theme.spacing(2)}; + padding-left: ${theme.spacing(4)}; + box-shadow: ${theme.shadows[4]}; + `, +); + +const MobileDialogTitle = createComponentSlot(Typography)({ + componentName: "DataGridPanel", + slotName: "mobileDialogTitle", +})(); + +const MobileDialogCloseButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "mobileDialogCloseButton", +})(css` + min-width: 0; // TODO: Should this be done in the theme? +`); + +const MobileDialogContent = createComponentSlot(DialogContent)({ + componentName: "DataGridPanel", + slotName: "mobileDialogContent", +})( + ({ theme }) => css` + padding: 0; + background-color: white; + + ${theme.breakpoints.up("sm")} { + padding: 0; + } + `, +); + +const MobileDialogFooter = createComponentSlot("div")({ + componentName: "DataGridPanel", + slotName: "mobileDialogFooter", +})(); + +const MobileAddFilterButtonWrapper = createComponentSlot("div")({ + componentName: "DataGridPanel", + slotName: "mobileAddFilterButtonWrapper", +})( + ({ theme }) => css` + position: relative; + display: flex; + justify-content: center; + padding: ${theme.spacing(2)} ${theme.spacing(4)}; + box-shadow: ${theme.shadows[4]}; + border-bottom: 1px solid ${theme.palette.divider}; + `, +); + +const MobileAddFilterButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "mobileAddFilterButton", +})(); + +const MobileDialogFooterActions = createComponentSlot("div")({ + componentName: "DataGridPanel", + slotName: "mobileDialogFooterActions", +})( + ({ theme }) => css` + display: flex; + justify-content: space-between; + padding: ${theme.spacing(4)}; + box-shadow: ${theme.shadows[4]}; + `, +); + +const ResetFiltersButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "resetFiltersButton", +})(); + +const ResetColumnsButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "resetColumnsButton", +})(); + +const ApplyButton = createComponentSlot(Button)({ + componentName: "DataGridPanel", + slotName: "applyButton", +})(); + +declare module "@mui/material/styles" { + interface ComponentsPropsList { + CometAdminDataGridPanel: DataGridPanelProps; + } + + interface ComponentNameToClassKey { + CometAdminDataGridPanel: DataGridPanelClassKey; + } + + interface Components { + CometAdminDataGridPanel?: { + defaultProps?: Partial; + styleOverrides?: ComponentsOverrides["CometAdminDataGridPanel"]; + }; + } +} diff --git a/packages/admin/admin/src/form/fields/CheckboxField.tsx b/packages/admin/admin/src/form/fields/CheckboxField.tsx index 46baad8e03..97a35b36b7 100644 --- a/packages/admin/admin/src/form/fields/CheckboxField.tsx +++ b/packages/admin/admin/src/form/fields/CheckboxField.tsx @@ -1,10 +1,11 @@ import { FormControlLabel, type FormControlLabelProps } from "@mui/material"; +import { type ReactNode } from "react"; import { FinalFormCheckbox, type FinalFormCheckboxProps } from "../Checkbox"; import { Field, type FieldProps } from "../Field"; export interface CheckboxFieldProps extends FieldProps { - fieldLabel?: string; + fieldLabel?: ReactNode; componentsProps?: { formControlLabel?: FormControlLabelProps; finalFormCheckbox?: FinalFormCheckboxProps; diff --git a/packages/admin/admin/src/form/fields/SwitchField.tsx b/packages/admin/admin/src/form/fields/SwitchField.tsx index 88210cd69a..c7d0197544 100644 --- a/packages/admin/admin/src/form/fields/SwitchField.tsx +++ b/packages/admin/admin/src/form/fields/SwitchField.tsx @@ -1,10 +1,13 @@ import { FormControlLabel, type FormControlLabelProps } from "@mui/material"; +import { type ReactNode } from "react"; import { Field, type FieldProps } from "../Field"; import { FinalFormSwitch, type FinalFormSwitchProps } from "../Switch"; -export interface SwitchFieldProps extends FieldProps { - fieldLabel?: string; +export interface SwitchFieldProps extends Omit, "label"> { + name: string; + fieldLabel?: ReactNode; + label?: ReactNode | ((checked?: boolean) => ReactNode); componentsProps?: { formControlLabel?: FormControlLabelProps; finalFormSwitch?: FinalFormSwitchProps; @@ -16,7 +19,11 @@ export const SwitchField = ({ fieldLabel, label, componentsProps = {}, ...restPr return ( {(props) => ( - } {...formControlLabelProps} /> + } + {...formControlLabelProps} + /> )} ); diff --git a/packages/admin/admin/src/index.ts b/packages/admin/admin/src/index.ts index 517120af07..bb371466a6 100644 --- a/packages/admin/admin/src/index.ts +++ b/packages/admin/admin/src/index.ts @@ -54,6 +54,7 @@ export { CrudContextMenu, CrudContextMenuClassKey, CrudContextMenuProps } from " export { CrudMoreActionsMenuClassKey } from "./dataGrid/CrudMoreActionsMenu"; export { CrudMoreActionsMenu, CrudMoreActionsMenuContext, CrudMoreActionsMenuItem, CrudMoreActionsMenuProps } from "./dataGrid/CrudMoreActionsMenu"; export { CrudVisibility, CrudVisibilityProps } from "./dataGrid/CrudVisibility"; +export { DataGridPanel, DataGridPanelClassKey, DataGridPanelProps } from "./dataGrid/DataGridPanel"; export { ExportApi, useDataGridExcelExport } from "./dataGrid/excelExport/useDataGridExcelExport"; export { GridCellContent, GridCellContentClassKey, GridCellContentProps } from "./dataGrid/GridCellContent"; export { GridActionsColDef, GridBaseColDef, GridColDef, GridSingleSelectColDef } from "./dataGrid/GridColDef"; diff --git a/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx b/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx index 30b19061d6..553b87d5c7 100644 --- a/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx +++ b/packages/admin/admin/src/theme/componentsTheme/MuiDataGrid.tsx @@ -1,15 +1,14 @@ import { ArrowDown, ArrowUp, Check, Clear, Close, Delete, MoreVertical, Search } from "@comet/admin-icons"; import { buttonBaseClasses, - buttonClasses, - formControlClasses, + getSwitchUtilityClass, iconButtonClasses, - inputAdornmentClasses, inputBaseClasses, - inputClasses, inputLabelClasses, + nativeSelectClasses, svgIconClasses, type SvgIconProps, + switchClasses, TextField, type TextFieldProps, } from "@mui/material"; @@ -31,16 +30,20 @@ const getDensityHeightValue = (density: string | unknown, spacing: Spacing) => { } }; -export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { palette, shadows, spacing }) => ({ +const filtersLeftSectionWidth = 120; +const filterDeleteIconSize = 32; +const filterLeftSectionGap = 5; +const filterOperatorInputWidth = filtersLeftSectionWidth - filterDeleteIconSize - filterLeftSectionGap; + +export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { palette, shadows, spacing, breakpoints }) => ({ ...component, defaultProps: { ...component?.defaultProps, disableRowSelectionOnClick: true, slots: { - /* @TODO: add FilterPanelAddIcon to display Comet Add Icon once MUI Datagrid is updated to v6 or higher */ QuickFilterIcon: Search, QuickFilterClearIcon: Clear, - FilterPanelDeleteIcon: Delete, + FilterPanelDeleteIcon: (props: SvgIconProps) => , BooleanCellTrueIcon: Check, BooleanCellFalseIcon: Close, ColumnSortedAscendingIcon: ArrowUp, @@ -49,8 +52,16 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { ColumnMenuIcon: (props: SvgIconProps) => , ...component?.defaultProps?.slots, }, + slotProps: { + ...component?.defaultProps?.slotProps, + baseButton: { + color: "info", + ...component?.defaultProps?.slotProps?.baseButton, + }, + }, localeText: { noRowsLabel: GRID_DEFAULT_LOCALE_TEXT.noResultsOverlayLabel, + columnsPanelTextFieldLabel: "", ...component?.defaultProps?.localeText, }, }, @@ -69,6 +80,34 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { }, }, }, + panelHeader: { + padding: `4px 4px ${spacing(1)} 4px`, + borderBottom: `1px solid ${palette.divider}`, + }, + columnsManagement: { + padding: 0, + }, + columnsManagementRow: { + marginBottom: spacing(2), + + "&:last-child": { + marginBottom: 0, + }, + + [`& .${switchClasses.root}`]: { + marginRight: 0, + }, + [`& .${switchClasses.root} .${switchClasses.thumb}`]: { + width: 10, + height: 10, + }, + [`& .${switchClasses.root} .${switchClasses.switchBase}`]: { + padding: 3, + }, + [`& .${switchClasses.root} .${switchClasses.switchBase}.${getSwitchUtilityClass("checked")}`]: { + transform: "translateX(20px)", + }, + }, columnHeader: ({ ownerState }) => ({ /* !important is required to override inline styles */ height: `${getDensityHeightValue(ownerState?.density, spacing)} !important`, @@ -124,126 +163,124 @@ export const getMuiDataGrid: GetMuiComponentTheme<"MuiDataGrid"> = (component, { height: "20px", marginRight: "10px", }, - panel: { - ["@media (max-width: 900px)"]: { - width: "100%", - transform: "translate3d(0,0,0)", - }, - }, panelContent: { - padding: spacing(1, 0), - [`& .${gridClasses.filterForm}:first-child .${gridClasses.filterFormLogicOperatorInput}`]: { - display: "flex", - }, - ["@media (max-width: 900px)"]: { - maxHeight: "none", - padding: 0, - }, + padding: spacing(4), + }, + paper: { + border: `1px solid ${palette.grey[100]}`, + boxShadow: shadows[4], + borderRadius: 4, + maxHeight: "none", + flexDirection: "column", }, filterForm: { - margin: spacing(5, 4, 0, 4), - padding: spacing(2, 1), - gap: "5px", - borderBottom: `1px solid ${palette.grey[50]}`, - ["@media (max-width: 900px)"]: { - flexDirection: "row", - flexWrap: "wrap", - margin: spacing(4, 4, 0, 4), - gap: 0, - padding: 0, - paddingBottom: spacing(5), - "&:last-child": { - marginBottom: 0, - paddingBottom: 0, - }, - }, - "&:last-child": { - border: "none", + flexDirection: "row", + flexWrap: "wrap", + padding: 0, + + [`${breakpoints.up("md")}`]: { + flexWrap: "nowrap", + gap: spacing(1), }, - [`.${formControlClasses.root}`]: { - marginRight: 0, + + ["&:not(:last-child)"]: { + paddingBottom: spacing(4), + marginBottom: spacing(4), + borderBottom: `1px solid ${palette.divider}`, + + [`${breakpoints.up("md")}`]: { + paddingBottom: spacing(2), + marginBottom: spacing(2), + borderBottomColor: palette.grey[50], + }, }, - [`.${iconButtonClasses.root}`]: { - height: 32, - width: 32, + + [`&:first-child .${gridClasses.filterFormOperatorInput}`]: { + // The first "Operator"-select is fully hidden by default when there is only one filter. + // Setting `display: block` makes sure it takes up it's space as if it were visible to prevent the alignment from breaking. + // Even though `display: block` is set now, it's still not visible, due to it's default styling of `visibility: hidden`. + display: "block", }, + [`.${inputLabelClasses.root}`]: { - transform: "translateY(-22px)", + position: "static", + transform: "none", fontSize: 14, - ["@media (max-width: 900px)"]: { - position: "relative", - transform: "unset", - }, - }, - [`.${inputClasses.root}`]: { - marginTop: 0, + fontWeight: 600, }, - [`& .${inputAdornmentClasses.root}`]: { - padding: spacing(0, 1, 0, 0), - }, - }, - filterFormLogicOperatorInput: { - ["@media (max-width: 900px)"]: { - padding: spacing(2, 1), - width: "27.2%", + + [`.${nativeSelectClasses.select}`]: { + whiteSpace: "nowrap", + textOverflow: "ellipsis", }, }, filterFormDeleteIcon: { + width: filterDeleteIconSize, + height: filterDeleteIconSize, + marginRight: filterLeftSectionGap, + marginTop: "auto", + marginBottom: 3, justifyContent: "center", - [`& .${svgIconClasses.root}`]: { - width: 16, - height: 16, + [`${breakpoints.up("md")}`]: { + marginRight: 0, }, - ["@media (max-width: 900px)"]: { - padding: spacing(2, 1), - alignItems: "flex-start", - justifyContent: "flex-end", - width: "11.1%", + [`& > .${iconButtonClasses.root}`]: { + height: "100%", }, }, - panelFooter: { - borderTop: `1px solid ${palette.grey[100]}`, - padding: "7px 0", - [`.${buttonClasses.root}`]: { - color: palette.primary.main, - }, - ["@media (max-width: 900px)"]: { - justifyContent: "center", - boxShadow: shadows[4], + filterFormLogicOperatorInput: { + width: filterOperatorInputWidth, + marginRight: 0, + + [`${breakpoints.up("md")}`]: { + width: 80, }, }, filterFormColumnInput: { - marginRight: spacing(4), + width: `calc(100% - ${filtersLeftSectionWidth}px)`, + paddingLeft: spacing(2), + boxSizing: "border-box", - ["@media (max-width: 900px)"]: { - padding: spacing(2, 1), - width: "61.6%", + [`${breakpoints.up("md")}`]: { + width: 199, + paddingLeft: 0, }, }, filterFormOperatorInput: { - margin: spacing(0, 4, 0, 0), + marginTop: spacing(3), + flexBasis: filterOperatorInputWidth, + flexGrow: 1, - ["@media (max-width: 900px)"]: { - padding: spacing(2, 1), - width: "38.3%", + [`${breakpoints.up("md")}`]: { + marginTop: 0, + width: 110, }, }, filterFormValueInput: { - ["@media (max-width: 900px)"]: { - padding: spacing(2, 1), - width: "61.6%", + width: `calc(100% - ${filtersLeftSectionWidth}px)`, + paddingLeft: spacing(2), + boxSizing: "border-box", + marginTop: spacing(3), + + [`${breakpoints.up("md")}`]: { + width: 199, + paddingLeft: 0, + marginTop: 0, }, - }, - paper: { - boxShadow: shadows[4], - border: `1px solid ${palette.divider}`, - borderRadius: "4px", - ["@media (max-width: 900px)"]: { - height: "100%", - maxHeight: "none", + + "&:empty": { + display: "none", // Make space for `filterFormOperatorInput` to expand and take up the full width }, + + [`& .${inputBaseClasses.root}`]: { + marginTop: 0, + }, + }, + panelFooter: { + padding: spacing(2), + borderTop: `1px solid ${palette.divider}`, }, // @ts-expect-error This key exists but is missing in the types. toolbarQuickFilter: { diff --git a/packages/admin/cms-admin/CHANGELOG.md b/packages/admin/cms-admin/CHANGELOG.md index 21333d3210..065dc906a0 100644 --- a/packages/admin/cms-admin/CHANGELOG.md +++ b/packages/admin/cms-admin/CHANGELOG.md @@ -1,5 +1,24 @@ # @comet/cms-admin +## 7.15.0 + +### Patch Changes + +- 46ab330da: Adapt styling of the dashboard header to match the Comet DXP design +- Updated dependencies [e056e8f3d] +- Updated dependencies [a189d4ed9] +- Updated dependencies [faa54eb8e] +- Updated dependencies [7d8c36e6c] +- Updated dependencies [a189d4ed9] +- Updated dependencies [6827982fe] +- Updated dependencies [7d8c36e6c] + - @comet/blocks-admin@7.15.0 + - @comet/admin@7.15.0 + - @comet/admin-theme@7.15.0 + - @comet/admin-date-time@7.15.0 + - @comet/admin-icons@7.15.0 + - @comet/admin-rte@7.15.0 + ## 7.14.0 ### Minor Changes diff --git a/packages/admin/cms-admin/package.json b/packages/admin/cms-admin/package.json index f1e6a71adc..60929d6d72 100644 --- a/packages/admin/cms-admin/package.json +++ b/packages/admin/cms-admin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-admin", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/admin/cms-admin", "type": "git", @@ -32,10 +32,10 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin": "workspace:^7.14.0", - "@comet/admin-date-time": "workspace:^7.14.0", - "@comet/admin-icons": "workspace:^7.14.0", - "@comet/admin-rte": "workspace:^7.14.0", + "@comet/admin": "workspace:^7.15.0", + "@comet/admin-date-time": "workspace:^7.15.0", + "@comet/admin-icons": "workspace:^7.15.0", + "@comet/admin-rte": "workspace:^7.15.0", "axios": "^0.29.0", "change-case": "^4.1.2", "class-validator": "^0.14.1", @@ -67,9 +67,9 @@ "@apollo/client": "^3.13.1", "@babel/cli": "^7.26.4", "@babel/core": "^7.26.9", - "@comet/admin-babel-preset": "workspace:^7.14.0", - "@comet/cli": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/admin-babel-preset": "workspace:^7.15.0", + "@comet/cli": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@graphql-codegen/cli": "^5.0.5", @@ -117,7 +117,6 @@ "react-router": "^5.3.4", "react-router-dom": "^5.3.4", "ts-jest": "^29.2.6", - "ts-node": "^10.9.2", "typescript": "^5.7.3" }, "peerDependencies": { diff --git a/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx b/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx index 60c4cbf4fc..bed83b6265 100644 --- a/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx +++ b/packages/admin/cms-admin/src/blocks/factories/createColumnsBlock.tsx @@ -392,10 +392,7 @@ export function createColumnsBlock( - + @@ -410,7 +407,7 @@ export function createColumnsBlock( size="large" startIcon={} > - + )} diff --git a/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx b/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx index ff307e761d..03d4deb348 100644 --- a/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx +++ b/packages/admin/cms-admin/src/blocks/helpers/VideoOptionsFields.tsx @@ -1,5 +1,4 @@ -import { Field, FinalFormSwitch, OnChangeField } from "@comet/admin"; -import { FormControlLabel } from "@mui/material"; +import { OnChangeField, SwitchField } from "@comet/admin"; import { useForm } from "react-final-form"; import { FormattedMessage } from "react-intl"; @@ -7,30 +6,9 @@ export const VideoOptionsFields = () => { const form = useForm(); return ( <> - - {(props) => ( - } - control={} - /> - )} - - - {(props) => ( - } - control={} - /> - )} - - - {(props) => ( - } - control={} - /> - )} - + } /> + } /> + } /> {/* case: autoplay = false and showControls = false is not allowed */} {(value, previousValue) => { diff --git a/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx b/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx index d2ee1602f8..a2a1223133 100644 --- a/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx +++ b/packages/admin/cms-admin/src/dam/DataGrid/fileUpload/useDamFileUpload.tsx @@ -435,11 +435,11 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi = uploadedFiles.push({ id: response.data.id, parentId: targetFolderId, type: "file", file }); } catch (err) { errorOccurred = true; - const typedErr = err as AxiosError<{ error: string; message: string; statusCode: number }>; + const typedErr = err as AxiosError<{ error: string; message: string; statusCode: number } | string>; - if (typedErr.response?.data.error === "CometImageResolutionException") { + if (hasObjectErrorData(typedErr) && typedErr.response?.data.error === "CometImageResolutionException") { addValidationError(file, ); - } else if (typedErr.response?.data.error === "CometValidationException") { + } else if (hasObjectErrorData(typedErr) && typedErr.response?.data.error === "CometValidationException") { const message = typedErr.response.data.message; const extension = `.${file.name.split(".").pop()}`; @@ -454,7 +454,7 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi = } else { addValidationError(file, ); } - } else if (typedErr.response?.data.message.includes("SVG contains forbidden content")) { + } else if (hasStringErrorData(typedErr) && typedErr.response?.data.includes("SVG contains forbidden content")) { addValidationError(file, ); } else if (typedErr.response === undefined && typedErr.request) { addValidationError(file, ); @@ -507,3 +507,13 @@ export const useDamFileUpload = (options: UploadDamFileOptions): FileUploadApi = newlyUploadedItems, }; }; + +const hasObjectErrorData = ( + err: AxiosError<{ error: string; message: string; statusCode: number } | string>, +): err is AxiosError<{ error: string; message: string; statusCode: number }> => { + return typeof err.response?.data === "object" && err.response?.data.error !== undefined; +}; + +const hasStringErrorData = (err: AxiosError<{ error: string; message: string; statusCode: number } | string>): err is AxiosError => { + return typeof err.response?.data === "string"; +}; diff --git a/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx b/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx index fff6a4bc96..f58f7e76e5 100644 --- a/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx +++ b/packages/admin/cms-admin/src/dam/DataGrid/filter/DamTableFilter.tsx @@ -3,12 +3,11 @@ import { FilterBar, FilterBarPopoverFilter, FinalFormSearchTextField, - FinalFormSwitch, type IFilterApi, type ISortInformation, + SwitchField, TableFilterFinalForm, } from "@comet/admin"; -import { FormControlLabel } from "@mui/material"; import { FormattedMessage, useIntl } from "react-intl"; import { type DamFilter } from "../../DamTable"; @@ -37,14 +36,10 @@ export const DamTableFilter = ({ filterApi, hideArchiveFilter }: DamTableFilterP label={intl.formatMessage({ id: "comet.pages.dam.archived", defaultMessage: "Archived" })} sx={{ marginRight: 2, marginLeft: 2 }} > - - {(props) => ( - } - label={} - /> - )} - + } + /> )} name="sort"> diff --git a/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx b/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx index 35e9cf9b74..e89f79608e 100644 --- a/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx +++ b/packages/admin/cms-admin/src/dashboard/DashboardHeader.tsx @@ -30,7 +30,7 @@ type RootProps = { const Root = styled("div")` position: relative; - height: 300px; + height: 230px; color: ${({ textColor }) => (textColor === "light" ? "white" : textColor === "dark" ? "black" : "inherit")}; background-color: ${({ theme }) => theme.palette.grey[700]}; @@ -45,13 +45,25 @@ const Root = styled("div")` background-image: url(${backgroundImageUrl["2x"]}); } `} + + ${({ theme }) => theme.breakpoints.up("sm")} { + height: 300px; + } `; const Greeting = styled(Typography)` position: absolute; - left: ${({ theme }) => theme.spacing(8)}; - bottom: ${({ theme }) => theme.spacing(8)}; - font-size: 55px; - line-height: 64px; - font-weight: 200; + left: ${({ theme }) => theme.spacing(4)}; + bottom: 12px; + font-size: 30px; + line-height: 38px; + font-weight: 160; + + ${({ theme }) => theme.breakpoints.up("sm")} { + left: ${({ theme }) => theme.spacing(8)}; + bottom: ${({ theme }) => theme.spacing(8)}; + font-size: 55px; + line-height: 64px; + font-weight: 200; + } `; diff --git a/packages/admin/cms-admin/src/dashboard/DateTime.tsx b/packages/admin/cms-admin/src/dashboard/DateTime.tsx index ba09030d63..6ffa15e1f3 100644 --- a/packages/admin/cms-admin/src/dashboard/DateTime.tsx +++ b/packages/admin/cms-admin/src/dashboard/DateTime.tsx @@ -29,18 +29,34 @@ export const DateTime = () => { const Root = styled("div")` position: absolute; - top: ${({ theme }) => theme.spacing(4)}; - right: ${({ theme }) => theme.spacing(8)}; - font-weight: 200; + top: 16px; + right: ${({ theme }) => theme.spacing(4)}; text-align: right; + + ${({ theme }) => theme.breakpoints.up("sm")} { + top: ${({ theme }) => theme.spacing(4)}; + right: ${({ theme }) => theme.spacing(8)}; + } `; const DateContainer = styled("div")` - font-size: 33px; - line-height: 39px; + font-size: 24px; + line-height: 28px; + font-weight: 150; + + ${({ theme }) => theme.breakpoints.up("sm")} { + font-size: 33px; + line-height: 39px; + } `; const TimeContainer = styled("div")` - font-size: 55px; - line-height: 64px; + font-size: 36px; + line-height: 42px; + font-weight: 170; + + ${({ theme }) => theme.breakpoints.up("sm")} { + font-size: 55px; + line-height: 64px; + } `; diff --git a/packages/api/cms-api/CHANGELOG.md b/packages/api/cms-api/CHANGELOG.md index 7d6bc94388..a70b675a47 100644 --- a/packages/api/cms-api/CHANGELOG.md +++ b/packages/api/cms-api/CHANGELOG.md @@ -1,5 +1,18 @@ # @comet/cms-api +## 7.15.0 + +### Patch Changes + +- 83b8111d6: Allow `use` tag in SVG again + + `use` can be used to define paths once in a SVG and then integrating them multiple times via anchor links: ``. This should not be prohibited. + + It's still not possible to use `use` to reference external files, since we still prohibit `href` and `xlink:href` attributes starting with `http://`, `https://` and `javascript:`. + +- e6f9641db: Add fallback values for users created via ID token + - @comet/blocks-api@7.15.0 + ## 7.14.0 ### Minor Changes diff --git a/packages/api/cms-api/package.json b/packages/api/cms-api/package.json index eea33d916e..01c6746f94 100644 --- a/packages/api/cms-api/package.json +++ b/packages/api/cms-api/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-api", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/api/cms-api", "type": "git", @@ -69,7 +69,7 @@ }, "devDependencies": { "@aws-sdk/types": "^3.734.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/eslint-config": "workspace:^7.15.0", "@golevelup/ts-jest": "^0.6.2", "@kubernetes/client-node": "^1.0.0", "@mikro-orm/core": "^6.4.7", diff --git a/packages/api/cms-api/src/dam/files/files.utils.ts b/packages/api/cms-api/src/dam/files/files.utils.ts index dcf60f8a0e..31cad371b5 100644 --- a/packages/api/cms-api/src/dam/files/files.utils.ts +++ b/packages/api/cms-api/src/dam/files/files.utils.ts @@ -52,7 +52,6 @@ type SvgNode = const disallowedSvgTags = [ "script", // can lead to XSS "foreignObject", // can embed non-SVG content - "use", // can load external resources "image", // can load external resources "animate", // can modify attributes; resource exhaustion "animateMotion", // can modify attributes; resource exhaustion diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 79406c08ea..e2cb0b1204 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/cli +## 7.15.0 + ## 7.14.0 ## 7.13.0 diff --git a/packages/cli/package.json b/packages/cli/package.json index 2e63863120..e36df3a1b8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cli", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/cli", "type": "git", @@ -31,7 +31,7 @@ "ts-node": "^10.9.2" }, "devDependencies": { - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/eslint-config": "workspace:^7.15.0", "@types/node": "^22.13.5", "eslint": "^9.20.1", "npm-run-all2": "^5.0.2", diff --git a/packages/eslint-config/CHANGELOG.md b/packages/eslint-config/CHANGELOG.md index 06d5fbce30..6178665ef3 100644 --- a/packages/eslint-config/CHANGELOG.md +++ b/packages/eslint-config/CHANGELOG.md @@ -1,5 +1,11 @@ # @comet/eslint-config +## 7.15.0 + +### Patch Changes + +- @comet/eslint-plugin@7.15.0 + ## 7.14.0 ### Patch Changes diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index d70c84ce00..ea91b87a96 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@comet/eslint-config", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/eslint-config", "type": "git", @@ -30,8 +30,8 @@ "dependencies": { "@calm/eslint-plugin-react-intl": "^1.4.1", "@eslint/js": "^9.21.0", - "@next/eslint-plugin-next": "^15.1.7", - "eslint-config-next": "^15.1.7", + "@next/eslint-plugin-next": "^15.2.0", + "eslint-config-next": "^15.2.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-formatjs": "^5.2.14", "eslint-plugin-jsonc": "^2.19.1", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 46d0ffe5d0..12a1690025 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/eslint-plugin +## 7.15.0 + ## 7.14.0 ## 7.13.0 diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c1c7728e4c..9b466ba992 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/eslint-plugin", - "version": "7.14.0", + "version": "7.15.0", "main": "lib/index.js", "scripts": { "build": "$npm_execpath run clean && tsc", diff --git a/packages/site/cms-site/CHANGELOG.md b/packages/site/cms-site/CHANGELOG.md index 904bc595a9..ed77726873 100644 --- a/packages/site/cms-site/CHANGELOG.md +++ b/packages/site/cms-site/CHANGELOG.md @@ -1,5 +1,11 @@ # @comet/cms-site +## 7.15.0 + +### Patch Changes + +- 75fb1d0d4: Fix block preview not rendering before user interaction + ## 7.14.0 ### Minor Changes diff --git a/packages/site/cms-site/package.json b/packages/site/cms-site/package.json index 55be42c73f..67fdf7f34f 100644 --- a/packages/site/cms-site/package.json +++ b/packages/site/cms-site/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-site", - "version": "7.14.0", + "version": "7.15.0", "repository": { "directory": "packages/site/cms-site", "type": "git", @@ -34,8 +34,8 @@ "usehooks-ts": "^3.1.1" }, "devDependencies": { - "@comet/cli": "workspace:^7.14.0", - "@comet/eslint-config": "workspace:^7.14.0", + "@comet/cli": "workspace:^7.15.0", + "@comet/eslint-config": "workspace:^7.15.0", "@types/draft-js": "^0.11.18", "@types/jest": "^29.5.14", "@types/lodash.isequal": "^4.5.8", diff --git a/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx b/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx index 1fb6661502..2ec230ccd6 100644 --- a/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx +++ b/packages/site/cms-site/src/iframebridge/useBlockPreviewFetch.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useState } from "react"; import { createFetchInMemoryCache } from "../graphQLFetch/fetchInMemoryCache"; import { convertPreviewDataToHeaders, createFetchWithDefaults, createGraphQLFetch, type GraphQLFetch } from "../graphQLFetch/graphQLFetch"; @@ -16,16 +16,19 @@ export function useBlockPreviewFetch(apiUrl: string): { fetch: Fetch; graphQLFet export function useBlockPreviewFetch(apiUrl?: string | undefined): { fetch: Fetch; graphQLFetch?: GraphQLFetch }; export function useBlockPreviewFetch(apiUrl?: string | undefined) { const { showOnlyVisible, graphQLApiUrl } = useIFrameBridge(); + const [graphQLFetch, setGraphQLFetch] = useState(() => + apiUrl ? createBlockPreviewFetch(apiUrl, !showOnlyVisible) : undefined, + ); - const graphQLFetchRef = useRef(apiUrl ? createBlockPreviewFetch(apiUrl, !showOnlyVisible) : undefined); useEffect(() => { if (graphQLApiUrl) { - graphQLFetchRef.current = createBlockPreviewFetch(graphQLApiUrl, !showOnlyVisible); + // We need to use an updater function here because createBlockPreviewFetch's return value would otherwise be incorrectly treated as an updater function. + setGraphQLFetch(() => createBlockPreviewFetch(graphQLApiUrl, !showOnlyVisible)); } }, [showOnlyVisible, graphQLApiUrl]); return { - graphQLFetch: graphQLFetchRef.current, + graphQLFetch, fetch: cachingFetch, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fa1c4ca17..125a979387 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -327,6 +327,9 @@ importers: class-validator: specifier: ^0.14.1 version: 0.14.1 + cli-progress: + specifier: ^3.12.0 + version: 3.12.0 compression: specifier: ^1.8.0 version: 1.8.0 @@ -379,6 +382,9 @@ importers: '@nestjs/schematics': specifier: ^11.0.1 version: 11.0.1(chokidar@4.0.3)(typescript@5.7.3) + '@types/cli-progress': + specifier: ^3.11.6 + version: 3.11.6 '@types/compression': specifier: ^1.7.5 version: 1.7.5 @@ -806,7 +812,7 @@ importers: packages/admin/admin: dependencies: '@comet/admin-icons': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-icons '@mui/utils': specifier: ^6.4.3 @@ -855,13 +861,13 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@comet/eslint-plugin': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-plugin '@emotion/react': specifier: ^11.14.0 @@ -1011,10 +1017,10 @@ importers: packages/admin/admin-color-picker: dependencies: '@comet/admin': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-icons react-colorful: specifier: ^5.6.1 @@ -1033,10 +1039,10 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@mui/material': specifier: ^6.4.5 @@ -1084,10 +1090,10 @@ importers: packages/admin/admin-date-time: dependencies: '@comet/admin': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-icons '@mui/utils': specifier: ^6.4.3 @@ -1106,10 +1112,10 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@mui/material': specifier: ^6.4.5 @@ -1261,10 +1267,10 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@mui/material': specifier: ^6.4.5 @@ -1318,10 +1324,10 @@ importers: packages/admin/admin-rte: dependencies: '@comet/admin': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-icons detect-browser: specifier: ^5.3.0 @@ -1346,10 +1352,10 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@mui/material': specifier: ^6.4.5 @@ -1412,16 +1418,16 @@ importers: packages/admin/cms-admin: dependencies: '@comet/admin': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin '@comet/admin-date-time': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-date-time '@comet/admin-icons': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-icons '@comet/admin-rte': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-rte axios: specifier: ^0.29.0 @@ -1512,13 +1518,13 @@ importers: specifier: ^7.26.9 version: 7.26.9 '@comet/admin-babel-preset': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../admin-babel-preset '@comet/cli': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../cli '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@emotion/react': specifier: ^11.14.0 @@ -1661,9 +1667,6 @@ importers: ts-jest: specifier: ^29.2.6 version: 29.2.6(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3)))(typescript@5.7.3) - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3) typescript: specifier: ^5.7.3 version: 5.7.3 @@ -1856,7 +1859,7 @@ importers: specifier: ^3.734.0 version: 3.734.0 '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@golevelup/ts-jest': specifier: ^0.6.2 @@ -1995,7 +1998,7 @@ importers: version: 10.9.2(@swc/core@1.10.11(@swc/helpers@0.5.5))(@types/node@22.13.5)(typescript@5.7.3) devDependencies: '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../eslint-config '@types/node': specifier: ^22.13.5 @@ -2022,11 +2025,11 @@ importers: specifier: ^9.21.0 version: 9.21.0 '@next/eslint-plugin-next': - specifier: ^15.1.7 - version: 15.1.7 + specifier: ^15.2.0 + version: 15.2.0 eslint-config-next: - specifier: ^15.1.7 - version: 15.1.7(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + specifier: ^15.2.0 + version: 15.2.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) eslint-config-prettier: specifier: ^10.0.1 version: 10.0.1(eslint@9.20.1(jiti@2.4.2)) @@ -2090,7 +2093,7 @@ importers: version: 10.0.1(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)) + version: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-jsonc: specifier: ^2.19.1 version: 2.19.1(eslint@9.20.1(jiti@2.4.2)) @@ -2150,10 +2153,10 @@ importers: version: 3.1.1(react@18.3.1) devDependencies: '@comet/cli': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../cli '@comet/eslint-config': - specifier: workspace:^7.14.0 + specifier: workspace:^7.15.0 version: link:../../eslint-config '@types/draft-js': specifier: ^0.11.18 @@ -5732,8 +5735,8 @@ packages: '@next/env@14.2.24': resolution: {integrity: sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==} - '@next/eslint-plugin-next@15.1.7': - resolution: {integrity: sha512-kRP7RjSxfTO13NE317ek3mSGzoZlI33nc/i5hs1KaWpK+egs85xg0DJ4p32QEiHnR0mVjuUfhRIun7awqfL7pQ==} + '@next/eslint-plugin-next@15.2.0': + resolution: {integrity: sha512-jHFUG2OwmAuOASqq253RAEG/5BYcPHn27p1NoWZDCf4OdvdK0yRYWX92YKkL+Mk2s+GyJrmd/GATlL5b2IySpw==} '@next/swc-darwin-arm64@14.2.24': resolution: {integrity: sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==} @@ -7901,14 +7904,6 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.24.0': - resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/eslint-plugin@8.24.1': resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7917,13 +7912,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.24.0': - resolution: {integrity: sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.24.1': resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7939,21 +7927,10 @@ packages: resolution: {integrity: sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.24.0': - resolution: {integrity: sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.24.1': resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.24.0': - resolution: {integrity: sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/type-utils@8.24.1': resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7969,10 +7946,6 @@ packages: resolution: {integrity: sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.24.0': - resolution: {integrity: sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.24.1': resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7989,12 +7962,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/typescript-estree@8.24.0': - resolution: {integrity: sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/typescript-estree@8.24.1': resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8015,13 +7982,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.24.0': - resolution: {integrity: sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.24.1': resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8037,10 +7997,6 @@ packages: resolution: {integrity: sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.24.0': - resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.24.1': resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -10234,8 +10190,8 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-config-next@15.1.7: - resolution: {integrity: sha512-zXoMnYUIy3XHaAoOhrcYkT9UQWvXqWju2K7NNsmb5wd/7XESDwof61eUdW4QhERr3eJ9Ko/vnXqIrj8kk/drYw==} + eslint-config-next@15.2.0: + resolution: {integrity: sha512-LkG0KKpinAoNPk2HXSx0fImFb/hQ6RnhSxTkpJFTkQ0SmnzsbRsjjN95WC/mDY34nKOenpptYEVvfkCR/h+VjA==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -11044,11 +11000,6 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@11.0.0: - resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} - engines: {node: 20 || >=22} - hasBin: true - glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} @@ -22075,7 +22026,7 @@ snapshots: '@next/env@14.2.24': {} - '@next/eslint-plugin-next@15.1.7': + '@next/eslint-plugin-next@15.2.0': dependencies: fast-glob: 3.3.1 @@ -24767,23 +24718,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - eslint: 9.20.1(jiti@2.4.2) - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -24801,18 +24735,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - eslint: 9.20.1(jiti@2.4.2) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.24.1 @@ -24835,27 +24757,11 @@ snapshots: '@typescript-eslint/types': 8.23.0 '@typescript-eslint/visitor-keys': 8.23.0 - '@typescript-eslint/scope-manager@8.24.0': - dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 - '@typescript-eslint/scope-manager@8.24.1': dependencies: '@typescript-eslint/types': 8.24.1 '@typescript-eslint/visitor-keys': 8.24.1 - '@typescript-eslint/type-utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - debug: 4.4.0 - eslint: 9.20.1(jiti@2.4.2) - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) @@ -24871,8 +24777,6 @@ snapshots: '@typescript-eslint/types@8.23.0': {} - '@typescript-eslint/types@8.24.0': {} - '@typescript-eslint/types@8.24.1': {} '@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)': @@ -24903,20 +24807,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.24.0(typescript@5.7.3)': - dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 8.24.1 @@ -24953,17 +24843,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.20.1(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - eslint: 9.20.1(jiti@2.4.2) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.20.1(jiti@2.4.2)) @@ -24985,11 +24864,6 @@ snapshots: '@typescript-eslint/types': 8.23.0 eslint-visitor-keys: 4.2.0 - '@typescript-eslint/visitor-keys@8.24.0': - dependencies: - '@typescript-eslint/types': 8.24.0 - eslint-visitor-keys: 4.2.0 - '@typescript-eslint/visitor-keys@8.24.1': dependencies: '@typescript-eslint/types': 8.24.1 @@ -27552,16 +27426,16 @@ snapshots: eslint: 9.20.1(jiti@2.4.2) semver: 7.7.1 - eslint-config-next@15.1.7(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3): + eslint-config-next@15.2.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3): dependencies: - '@next/eslint-plugin-next': 15.1.7 + '@next/eslint-plugin-next': 15.2.0 '@rushstack/eslint-patch': 1.10.5 - '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react-hooks: 5.1.0(eslint@9.20.1(jiti@2.4.2)) @@ -27602,7 +27476,7 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -27612,24 +27486,14 @@ snapshots: esquery: 1.6.0 jsonc-eslint-parser: 2.4.0 - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - eslint: 9.20.1(jiti@2.4.2) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -27650,7 +27514,7 @@ snapshots: - ts-jest - typescript - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -27661,36 +27525,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.20.1(jiti@2.4.2) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -28663,15 +28498,6 @@ snapshots: package-json-from-dist: 1.0.0 path-scurry: 1.11.1 - glob@11.0.0: - dependencies: - foreground-child: 3.2.1 - jackspeak: 4.0.2 - minimatch: 10.0.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.0 - path-scurry: 2.0.0 - glob@11.0.1: dependencies: foreground-child: 3.2.1 @@ -33445,7 +33271,7 @@ snapshots: rimraf@6.0.1: dependencies: - glob: 11.0.0 + glob: 11.0.1 package-json-from-dist: 1.0.0 robust-predicates@3.0.2: {} diff --git a/storybook/.storybook/preview.tsx b/storybook/.storybook/preview.tsx index 80ebe22548..7e485c9a17 100644 --- a/storybook/.storybook/preview.tsx +++ b/storybook/.storybook/preview.tsx @@ -1,8 +1,9 @@ import "@fontsource-variable/roboto-flex/full.css"; -import { createCometTheme, MainContent, MuiThemeProvider } from "@comet/admin"; +import { createCometTheme, DataGridPanel, MainContent, MuiThemeProvider } from "@comet/admin"; import { DateFnsLocaleProvider } from "@comet/admin-date-time"; import { createTheme as createMuiTheme, GlobalStyles } from "@mui/material"; +import type {} from "@mui/x-data-grid/themeAugmentation"; import type { Preview } from "@storybook/react"; import { type Locale as DateFnsLocale } from "date-fns"; import { de as deLocale, enUS as enLocale } from "date-fns/locale"; @@ -72,7 +73,21 @@ const preview: Preview = { decorators: [ (Story, context) => { const { theme: selectedTheme, locale: selectedLocale } = context.args; - const theme = selectedTheme === themeOptions.defaultMui ? createMuiTheme() : createCometTheme(); + const theme = + selectedTheme === themeOptions.defaultMui + ? createMuiTheme() + : createCometTheme({ + components: { + MuiDataGrid: { + defaultProps: { + slots: { + // @ts-expect-error @jamesricky fix this please + panel: DataGridPanel, + }, + }, + }, + }, + }); return ( diff --git a/storybook/src/admin/form/AllFieldComponents.stories.tsx b/storybook/src/admin/form/AllFieldComponents.stories.tsx index d5203d5da2..27dd6a9551 100644 --- a/storybook/src/admin/form/AllFieldComponents.stories.tsx +++ b/storybook/src/admin/form/AllFieldComponents.stories.tsx @@ -104,7 +104,7 @@ export const AllFieldComponents = { variant={fieldVariant} fullWidth /> - + (checked ? "On" : "Off")} fieldLabel="Switch" variant={fieldVariant} />
diff --git a/storybook/src/admin/form/Checkbox.stories.tsx b/storybook/src/admin/form/Checkbox.stories.tsx index c1f41056ba..84f5136e19 100644 --- a/storybook/src/admin/form/Checkbox.stories.tsx +++ b/storybook/src/admin/form/Checkbox.stories.tsx @@ -1,5 +1,5 @@ -import { Field, FieldContainer, FinalFormCheckbox } from "@comet/admin"; -import { Card, CardContent, FormControlLabel, Grid } from "@mui/material"; +import { CheckboxField, FieldContainer } from "@comet/admin"; +import { Card, CardContent, Grid } from "@mui/material"; import { Form } from "react-final-form"; export default { @@ -30,20 +30,10 @@ export const Checkbox = () => { - - {(props) => } />} - - - {(props) => } />} - - - {(props) => } />} - - - {(props) => ( - } /> - )} - + + + + diff --git a/storybook/src/admin/form/Switch.stories.tsx b/storybook/src/admin/form/Switch.stories.tsx index d71d40cc98..2a1b164cdb 100644 --- a/storybook/src/admin/form/Switch.stories.tsx +++ b/storybook/src/admin/form/Switch.stories.tsx @@ -1,5 +1,5 @@ -import { Field, FinalFormSwitch } from "@comet/admin"; -import { Box, Card, CardContent, Divider, FormControlLabel } from "@mui/material"; +import { SwitchField } from "@comet/admin"; +import { Card, CardContent } from "@mui/material"; import { Form } from "react-final-form"; export default { @@ -13,19 +13,18 @@ export const Switch = () => { onSubmit={(values) => { // }} - render={({ handleSubmit, values }) => ( + render={({ handleSubmit }) => (
- - {(props) => } />} - - - - - - {(props) => } />} - + + (checked ? "Yes" : "No")} + /> +
diff --git a/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx b/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx index 42654cb13e..75798f7e6b 100644 --- a/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx +++ b/storybook/src/admin/table/filterbar/AllFiltersInFilterbar.stories.tsx @@ -6,13 +6,13 @@ import { FilterBarPopoverFilter, FinalFormInput, FinalFormRangeInput, - FinalFormSwitch, + SwitchField, Table, TableFilterFinalForm, useTableQueryFilter, } from "@comet/admin"; import { faker } from "@faker-js/faker"; -import { Box, Divider, FormControlLabel, Typography } from "@mui/material"; +import { Box, Divider, Typography } from "@mui/material"; interface ColorFilterFieldProps { colors: string[]; @@ -128,9 +128,7 @@ function Story({ tableData }: StoryProps) { - - {(props) => } />} - + Show all articles that can be shipped with express delivery (usually shipped within 2-3 work days) diff --git a/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx b/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx index b2464e5ccf..a116668118 100644 --- a/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx +++ b/storybook/src/admin/table/filterbar/CombinedFieldsFilter.stories.tsx @@ -3,13 +3,13 @@ import { FilterBar, FilterBarPopoverFilter, FinalFormRangeInput, - FinalFormSwitch, + SwitchField, Table, TableFilterFinalForm, useTableQueryFilter, } from "@comet/admin"; import { faker } from "@faker-js/faker"; -import { Box, Divider, FormControlLabel, Typography } from "@mui/material"; +import { Box, Divider, Typography } from "@mui/material"; interface IFilterValues { expressDelivery: boolean; @@ -49,9 +49,7 @@ function Story({ tableData }: StoryProps) { - - {(props) => } />} - + Show all articles that can be shipped with express delivery (usually shipped within 2-3 work days) diff --git a/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx b/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx index 9ada179c88..0152394cca 100644 --- a/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx +++ b/storybook/src/admin/toolbar/DataGridToolbar.stories.tsx @@ -1,7 +1,18 @@ -import { DataGridToolbar, FillSpace, GridColumnsButton, GridFilterButton, StackLink, ToolbarActions, ToolbarItem } from "@comet/admin"; +import { + Button, + DataGridToolbar, + FillSpace, + GridColumnsButton, + GridFilterButton, + StackLink, + ToolbarActions, + ToolbarItem, + useDataGridRemote, + usePersistentColumnState, +} from "@comet/admin"; import { Add as AddIcon } from "@comet/admin-icons"; -import { Button } from "@mui/material"; -import { DataGrid, GridToolbarQuickFilter } from "@mui/x-data-grid"; +import { DataGrid as DataGridCommunity, GridToolbarQuickFilter } from "@mui/x-data-grid"; +import { DataGridPro } from "@mui/x-data-grid-pro"; import { storyRouterDecorator } from "../../story-router.decorator"; @@ -11,50 +22,62 @@ const data = [ { id: "e60900ec-3c69-4e67-8d78-83a10c7573d3", firstname: "Sophia", lastname: "Williams" }, ]; +const gridOptions = ["Community", "Pro"] as const; + export default { title: "@comet/admin/DataGridToolbar", decorators: [storyRouterDecorator()], + argTypes: { + gridVersion: { + name: "Data Grid Version", + control: "select", + options: gridOptions, + }, + }, + args: { gridVersion: gridOptions[0] }, }; export const _DataGridToolbar = { - render: () => { + render: ({ gridVersion }: { gridVersion: (typeof gridOptions)[number] }) => { + const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("FooBar") }; + const columns = [ { field: "firstname", headerName: "First Name", width: 150 }, { field: "lastname", headerName: "Last Name", width: 150 }, ]; + const Toolbar = () => { + return ( + + + + + + + + + + + + + + + + ); + }; + + const DataGrid = gridVersion === "Community" ? DataGridCommunity : DataGridPro; + return ( ( - - - - - - - - - - - - - - - - ), + toolbar: Toolbar, }} /> ); diff --git a/storybook/src/docs/form/Layout.stories.tsx b/storybook/src/docs/form/Layout.stories.tsx index cfe7540b7e..6da3a323e1 100644 --- a/storybook/src/docs/form/Layout.stories.tsx +++ b/storybook/src/docs/form/Layout.stories.tsx @@ -1,9 +1,9 @@ import { CancelButton, + CheckboxField, createCometTheme, Field, FieldContainer, - FinalFormCheckbox, FinalFormInput, FinalFormRadio, FinalFormSelect, @@ -104,9 +104,7 @@ export const FieldsInSidebar = { fullWidth component={FinalFormInput} /> - - {(props) => } />} - + <> {flavourOptions.map(({ value, label }) => ( @@ -165,9 +163,7 @@ export const FieldsInDialog = { fullWidth component={FinalFormInput} /> - - {(props) => } />} - + @@ -324,9 +320,7 @@ export const HorizontalFields = { ))} - - {(props) => } />} - + )} /> diff --git a/storybook/src/docs/form/components/FinalFormFields.stories.tsx b/storybook/src/docs/form/components/FinalFormFields.stories.tsx index 696513cb8d..7bc23f9b10 100644 --- a/storybook/src/docs/form/components/FinalFormFields.stories.tsx +++ b/storybook/src/docs/form/components/FinalFormFields.stories.tsx @@ -1,18 +1,18 @@ import { Button, + CheckboxField, Field, FieldContainer, FinalForm, FinalFormAsyncAutocomplete, FinalFormAsyncSelect, FinalFormAutocomplete, - FinalFormCheckbox, FinalFormInput, FinalFormRadio, FinalFormRangeInput, FinalFormSearchTextField, FinalFormSelect, - FinalFormSwitch, + SwitchField, } from "@comet/admin"; import { FormControlLabel } from "@mui/material"; import { useMemo } from "react"; @@ -227,12 +227,8 @@ export const _FinalFormCheckbox = { alert(JSON.stringify(values, null, 4)); }} > - - {(props) => } />} - - - {(props) => } />} - + + ); @@ -250,12 +246,14 @@ export const _FinalFormSwitch = { alert(JSON.stringify(values, null, 4)); }} > - - {(props) => } />} - - - {(props) => } />} - + (checked ? "On" : "Off")} fullWidth /> + (checked ? "On" : "Off")} + fullWidth + disabled + /> );