Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions demo/admin/src/common/blocks/ContactFormBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type BlockInterface, createBlockSkeleton } from "@comet/cms-admin";
import { type ContactFormBlockData, type ContactFormBlockInput } from "@src/blocks.generated";
import { FormattedMessage } from "react-intl";

export const ContactFormBlock: BlockInterface<ContactFormBlockData, Record<string, never>, ContactFormBlockInput> = {
...createBlockSkeleton(),
name: "ContactForm",
displayName: <FormattedMessage id="blocks.contactForm" defaultMessage="Contact Form" />,
defaultValues: () => ({}),
};
2 changes: 2 additions & 0 deletions demo/admin/src/documents/pages/blocks/PageContentBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnchorBlock, createBlocksBlock, DamImageBlock } from "@comet/cms-admin";
import { AccordionBlock } from "@src/common/blocks/AccordionBlock";
import { ContactFormBlock } from "@src/common/blocks/ContactFormBlock";
import { LayoutBlock } from "@src/common/blocks/LayoutBlock";
import { MediaGalleryBlock } from "@src/common/blocks/MediaGalleryBlock";
import { PageTreeIndexBlock } from "@src/common/blocks/PageTreeIndexBlock";
Expand Down Expand Up @@ -50,6 +51,7 @@ export const PageContentBlock = createBlocksBlock({
fullWidthImage: FullWidthImageBlock,
productList: ProductListBlock,
pageTreeIndex: PageTreeIndexBlock,
contactForm: ContactFormBlock,
},
additionalItemFields: {
...userGroupAdditionalItemFields,
Expand Down
11 changes: 9 additions & 2 deletions demo/api/block-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,11 @@
}
]
},
{
"name": "ContactForm",
"fields": [],
"inputFields": []
},
{
"name": "ContentGroup",
"fields": [
Expand Down Expand Up @@ -2106,7 +2111,8 @@
"textImage": "TextImage",
"fullWidthImage": "FullWidthImage",
"productList": "ProductList",
"pageTreeIndex": "PageTreeIndex"
"pageTreeIndex": "PageTreeIndex",
"contactForm": "ContactForm"
},
"nullable": false
},
Expand Down Expand Up @@ -2171,7 +2177,8 @@
"textImage": "TextImage",
"fullWidthImage": "FullWidthImage",
"productList": "ProductList",
"pageTreeIndex": "PageTreeIndex"
"pageTreeIndex": "PageTreeIndex",
"contactForm": "ContactForm"
},
"nullable": false
},
Expand Down
11 changes: 11 additions & 0 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ type BuildTemplate {
name: String!
}

input ContactFormArgs {
company: String
email: String!
message: String!
name: String!
phone: String
privacyConsent: Boolean!
subject: String!
}

type ContentScopeWithLabel {
label: JSONObject!
scope: JSONObject!
Expand Down Expand Up @@ -671,6 +681,7 @@ type Mutation {
saveLink(attachedPageTreeNodeId: ID, id: ID!, input: LinkInput!, lastUpdatedAt: DateTime): Link!
savePage(attachedPageTreeNodeId: ID, input: PageInput!, lastUpdatedAt: DateTime, pageId: ID!): Page!
savePredefinedPage(attachedPageTreeNodeId: ID!, id: ID!, input: PredefinedPageInput!, lastUpdatedAt: DateTime): PredefinedPage!
submitContactForm(input: ContactFormArgs!): Boolean!
triggerKubernetesCronJob(name: String!): KubernetesJob!
updateDamFile(id: ID!, input: UpdateDamFileInput!): DamFile!
updateDamFolder(id: ID!, input: UpdateDamFolderInput!): DamFolder!
Expand Down
2 changes: 2 additions & 0 deletions demo/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { Request } from "express";
import { AccessControlService } from "./auth/access-control.service";
import { AuthModule, SYSTEM_USER_NAME } from "./auth/auth.module";
import { UserService } from "./auth/user.service";
import { ContactFormModule } from "./contact-form/contact-form.module";
import { DamScope } from "./dam/dto/dam-scope";
import { DamFile } from "./dam/entities/dam-file.entity";
import { DamFolder } from "./dam/entities/dam-folder.entity";
Expand Down Expand Up @@ -219,6 +220,7 @@ export class AppModule {
OpenTelemetryModule,
...(config.sentry ? [SentryModule.forRootAsync(config.sentry)] : []),
WarningsModule,
ContactFormModule,
],
};
}
Expand Down
11 changes: 11 additions & 0 deletions demo/api/src/common/blocks/contact-form.block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BlockData, BlockInput, blockInputToData, createBlock } from "@comet/cms-api";

class ContactFormBlockData extends BlockData {}

class ContactFormBlockInput extends BlockInput {
transformToBlockData(): ContactFormBlockData {
return blockInputToData(ContactFormBlockData, this);
}
}

export const ContactFormBlock = createBlock(ContactFormBlockData, ContactFormBlockInput, "ContactForm");
36 changes: 36 additions & 0 deletions demo/api/src/contact-form/contact-form.args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { IsUndefinable } from "@comet/cms-api";
import { Field, InputType } from "@nestjs/graphql";
import { IsBoolean, IsEmail, IsString } from "class-validator";

@InputType()
export class ContactFormArgs {
@Field(() => String)
@IsString()
name: string;

@Field(() => String, { nullable: true })
@IsString()
@IsUndefinable()
company?: string;

@Field(() => String)
@IsEmail()
email: string;

@Field(() => String, { nullable: true })
@IsString()
@IsUndefinable()
phone?: string;

@Field(() => String)
@IsString()
subject: string;

@Field(() => String)
@IsString()
message: string;

@Field(() => Boolean)
@IsBoolean()
privacyConsent: boolean;
}
11 changes: 11 additions & 0 deletions demo/api/src/contact-form/contact-form.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { Module } from "@nestjs/common";

import { ContactFormResolver } from "./contact-form.resolver";

@Module({
imports: [MikroOrmModule.forFeature([])],
providers: [ContactFormResolver],
exports: [],
})
export class ContactFormModule {}
15 changes: 15 additions & 0 deletions demo/api/src/contact-form/contact-form.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DisablePermissionCheck, RequiredPermission } from "@comet/cms-api";
import { Injectable } from "@nestjs/common";
import { Args, Mutation, Resolver } from "@nestjs/graphql";

import { ContactFormArgs } from "./contact-form.args";

@Injectable()
@Resolver()
export class ContactFormResolver {
@RequiredPermission(DisablePermissionCheck, { skipScopeCheck: true })
@Mutation(() => Boolean)
async submitContactForm(@Args("input", { type: () => ContactFormArgs }) { ...args }: ContactFormArgs): Promise<boolean> {
return true;
}
}
2 changes: 2 additions & 0 deletions demo/api/src/db/fixtures/fixtures.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { YouTubeVideoBlockFixtureService } from "./generators/blocks/media/youtu
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 { ContactFormBlockFixtureService } from "./generators/blocks/navigation/contact-form-block-fixture.service";
import { LinkBlockFixtureService } from "./generators/blocks/navigation/link-block-fixture.service";
import { LinkListBlockFixtureService } from "./generators/blocks/navigation/link-list-block-fixture.service";
import { PageTreeIndexBlockFixtureService } from "./generators/blocks/navigation/page-tree-index-block-fixture.service";
Expand Down Expand Up @@ -79,6 +80,7 @@ import { VideoFixtureService } from "./generators/video-fixture.service";
CallToActionBlockFixtureService,
CallToActionListBlockFixtureService,
ColumnsBlockFixtureService,
ContactFormBlockFixtureService,
ContentGroupBlockFixtureService,
DamImageBlockFixtureService,
DamVideoBlockFixtureService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
import { Injectable } from "@nestjs/common";
import { ContactFormBlock } from "@src/common/blocks/contact-form.block";

@Injectable()
export class ContactFormBlockFixtureService {
async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof ContactFormBlock>> {
return {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FullWidthImageBlockFixtureService } from "./blocks/media/full-width-ima
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 { ContactFormBlockFixtureService } from "./blocks/navigation/contact-form-block-fixture.service";
import { LinkListBlockFixtureService } from "./blocks/navigation/link-list-block-fixture.service";
import { PageTreeIndexBlockFixtureService } from "./blocks/navigation/page-tree-index-block-fixture.service";
import { StandaloneCallToActionListBlockFixtureService } from "./blocks/navigation/standalone-call-to-action-list-block-fixture.service";
Expand Down Expand Up @@ -53,6 +54,7 @@ export class PageContentBlockFixtureService {
private readonly standaloneRichTextBlockFixtureService: StandaloneRichTextBlockFixtureService,
private readonly productListBlockFixtureService: ProductListBlockFixtureService,
private readonly pageTreeIndexBlockFixtureService: PageTreeIndexBlockFixtureService,
private readonly contactFormBlockFixtureService: ContactFormBlockFixtureService,
) {}

async generateBlockInput(blockCategory?: BlockCategory): Promise<ExtractBlockInputFactoryProps<typeof PageContentBlock>> {
Expand Down Expand Up @@ -82,6 +84,7 @@ export class PageContentBlockFixtureService {
richtext: ["textAndContent", this.standaloneRichTextBlockFixtureService],
textImage: ["textAndContent", this.textImageBlockFixtureService],
productList: ["textAndContent", this.productListBlockFixtureService],
contactForm: ["textAndContent", this.contactFormBlockFixtureService],
};

const supportedBlocksFixtureGenerators = Object.entries(fixtures)
Expand Down
2 changes: 2 additions & 0 deletions demo/api/src/documents/pages/blocks/page-content.block.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnchorBlock, BaseBlocksBlockItemData, BaseBlocksBlockItemInput, BlockField, createBlocksBlock, DamImageBlock } from "@comet/cms-api";
import { AccordionBlock } from "@src/common/blocks/accordion.block";
import { ContactFormBlock } from "@src/common/blocks/contact-form.block";
import { MediaGalleryBlock } from "@src/common/blocks/media-gallery.block";
import { PageTreeIndexBlock } from "@src/common/blocks/page-tree-index.block";
import { SpaceBlock } from "@src/common/blocks/space.block";
Expand Down Expand Up @@ -47,6 +48,7 @@ const supportedBlocks = {
fullWidthImage: FullWidthImageBlock,
productList: ProductListBlock,
pageTreeIndex: PageTreeIndexBlock,
contactForm: ContactFormBlock,
};

class BlocksBlockItemData extends BaseBlocksBlockItemData(supportedBlocks) {
Expand Down
4 changes: 3 additions & 1 deletion demo/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-focus-lock": "^2.13.6",
"react-hook-form": "^7.68.0",
"react-intl": "^7.1.11",
"redraft": "^0.10.2",
"swiper": "^12.0.3",
"usehooks-ts": "^3.1.1"
"usehooks-ts": "^3.1.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@babel/core": "^7.28.5",
Expand Down
73 changes: 73 additions & 0 deletions demo/site/src/app/api/contact-form/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { gql } from "@comet/site-nextjs";
import { createGraphQLFetch } from "@src/util/graphQLClient";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";

import { type GQLContactFormMutation, type GQLContactFormMutationVariables } from "./route.generated";

const contactFormMutation = gql`
mutation ContactForm($input: ContactFormArgs!) {
submitContactForm(input: $input)
}
`;

async function submitContactForm(values: GQLContactFormMutationVariables["input"]) {
const graphQLFetch = createGraphQLFetch();

const { submitContactForm } = await graphQLFetch<GQLContactFormMutation, GQLContactFormMutationVariables>(
contactFormMutation,
{
input: values,
},
{ method: "POST" },
);

return { submitContactForm };
}

const queryValidationSchema = z.object({
name: z.string(),
company: z.string().optional(),
email: z.string().email(),
phone: z.string().optional(),
subject: z.string(),
message: z.string(),
privacyConsent: z.boolean(),
});

export async function POST(request: NextRequest) {
const body = await request.json();

const validationResult = queryValidationSchema.safeParse({
name: body["name"],
company: body["company"],
email: body["email"],
phone: body["phone"],
subject: body["subject"],
message: body["message"],
privacyConsent: body["privacyConsent"],
});

if (!validationResult.success) {
return NextResponse.json(
{
cause: validationResult.error,
message: "Validation failed",
},
{
status: 400,
},
);
}

try {
const data = await submitContactForm(validationResult.data as GQLContactFormMutationVariables["input"]);

return NextResponse.json(data, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({ error: "Something went wrong processing the contact form" }, { status: 500 });
}
}
Loading