Skip to content

Commit

Permalink
Add phone and email link block (#2142)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Johannes Obermair <[email protected]>
Co-authored-by: Johannes Obermair <[email protected]>
  • Loading branch information
3 people authored Jun 25, 2024
1 parent 9055ff7 commit 73dfb61
Show file tree
Hide file tree
Showing 25 changed files with 398 additions and 14 deletions.
8 changes: 8 additions & 0 deletions .changeset/fifty-lemons-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@comet/cms-admin": minor
"@comet/blocks-api": minor
"@comet/cms-site": minor
"@comet/cms-api": minor
---

Add `PhoneLinkBlock` and `EmailLinkBlock`
5 changes: 5 additions & 0 deletions .changeset/purple-scissors-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@comet/cms-api": minor
---

Add `IsPhoneNumber` and `isPhoneNumber` validators to validate phone numbers
4 changes: 3 additions & 1 deletion demo/admin/src/common/blocks/LinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createLinkBlock, DamFileDownloadLinkBlock, ExternalLinkBlock, InternalLinkBlock } from "@comet/cms-admin";
import { createLinkBlock, DamFileDownloadLinkBlock, EmailLinkBlock, ExternalLinkBlock, InternalLinkBlock, PhoneLinkBlock } from "@comet/cms-admin";
import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock";

export const LinkBlock = createLinkBlock({
Expand All @@ -7,5 +7,7 @@ export const LinkBlock = createLinkBlock({
external: ExternalLinkBlock,
news: NewsLinkBlock,
damFileDownload: DamFileDownloadLinkBlock,
email: EmailLinkBlock,
phone: PhoneLinkBlock,
},
});
42 changes: 40 additions & 2 deletions demo/api/block-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,23 @@
}
]
},
{
"name": "EmailLink",
"fields": [
{
"name": "email",
"kind": "String",
"nullable": true
}
],
"inputFields": [
{
"name": "email",
"kind": "String",
"nullable": true
}
]
},
{
"name": "ExternalLink",
"fields": [
Expand Down Expand Up @@ -796,7 +813,9 @@
"internal": "InternalLink",
"external": "ExternalLink",
"news": "NewsLink",
"damFileDownload": "DamFileDownloadLink"
"damFileDownload": "DamFileDownloadLink",
"phone": "PhoneLink",
"email": "EmailLink"
},
"nullable": false
}
Expand Down Expand Up @@ -826,7 +845,9 @@
"internal": "InternalLink",
"external": "ExternalLink",
"news": "NewsLink",
"damFileDownload": "DamFileDownloadLink"
"damFileDownload": "DamFileDownloadLink",
"phone": "PhoneLink",
"email": "EmailLink"
},
"nullable": false
}
Expand Down Expand Up @@ -1259,6 +1280,23 @@
}
]
},
{
"name": "PhoneLink",
"fields": [
{
"name": "phone",
"kind": "String",
"nullable": false
}
],
"inputFields": [
{
"name": "phone",
"kind": "String",
"nullable": false
}
]
},
{
"name": "PixelImage",
"fields": [
Expand Down
11 changes: 9 additions & 2 deletions demo/api/src/common/blocks/linkBlock/link.block.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { ExternalLinkBlock } from "@comet/blocks-api";
import { createLinkBlock, DamFileDownloadLinkBlock, InternalLinkBlock } from "@comet/cms-api";
import { createLinkBlock, DamFileDownloadLinkBlock, EmailLinkBlock, InternalLinkBlock, PhoneLinkBlock } from "@comet/cms-api";
import { NewsLinkBlock } from "@src/news/blocks/news-link.block";

export const LinkBlock = createLinkBlock({
supportedBlocks: { internal: InternalLinkBlock, external: ExternalLinkBlock, news: NewsLinkBlock, damFileDownload: DamFileDownloadLinkBlock },
supportedBlocks: {
internal: InternalLinkBlock,
external: ExternalLinkBlock,
news: NewsLinkBlock,
damFileDownload: DamFileDownloadLinkBlock,
phone: PhoneLinkBlock,
email: EmailLinkBlock,
},
});
12 changes: 12 additions & 0 deletions demo/site/src/blocks/LinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
DamFileDownloadLinkBlock,
EmailLinkBlock,
ExternalLinkBlock,
InternalLinkBlock,
OneOfBlock,
PhoneLinkBlock,
PropsWithData,
SupportedBlocks,
withPreview,
Expand Down Expand Up @@ -32,6 +34,16 @@ const supportedBlocks: SupportedBlocks = {
{children}
</DamFileDownloadLinkBlock>
),
email: ({ children, title, ...props }) => (
<EmailLinkBlock data={props} title={title}>
{children}
</EmailLinkBlock>
),
phone: ({ children, title, ...props }) => (
<PhoneLinkBlock data={props} title={title}>
{children}
</PhoneLinkBlock>
),
};

interface LinkBlockProps extends PropsWithData<LinkBlockData> {
Expand Down
47 changes: 47 additions & 0 deletions packages/admin/cms-admin/src/blocks/EmailLinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Field, FinalFormInput } from "@comet/admin";
import { BlockCategory, BlockInterface, BlocksFinalForm, createBlockSkeleton, SelectPreviewComponent } from "@comet/blocks-admin";
import { isEmail } from "class-validator";
import * as React from "react";
import { FormattedMessage } from "react-intl";

import { EmailLinkBlockData, EmailLinkBlockInput } from "../blocks.generated";

export const EmailLinkBlock: BlockInterface<EmailLinkBlockData, EmailLinkBlockData, EmailLinkBlockInput> = {
...createBlockSkeleton(),

name: "EmailLink",

displayName: <FormattedMessage id="comet.blocks.link.email" defaultMessage="Email" />,

defaultValues: () => ({ email: undefined }),

category: BlockCategory.Navigation,

isValid: (state) => {
return state.email ? isEmail(state.email) : true;
},

AdminComponent: ({ state, updateState }) => {
return (
<SelectPreviewComponent>
<BlocksFinalForm onSubmit={updateState} initialValues={state}>
<Field
label={<FormattedMessage id="comet.blocks.link.email" defaultMessage="Email" />}
name="email"
component={FinalFormInput}
fullWidth
validate={(email: string) => {
if (email && !isEmail(email)) {
return <FormattedMessage id="comet.blocks.link.email.invalid" defaultMessage="Invalid e-mail address" />;
}
}}
/>
</BlocksFinalForm>
</SelectPreviewComponent>
);
},

previewContent: (state) => {
return state.email ? [{ type: "text", content: state.email }] : [];
},
};
44 changes: 44 additions & 0 deletions packages/admin/cms-admin/src/blocks/PhoneLinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Field, FinalFormInput } from "@comet/admin";
import { BlockCategory, BlockInterface, BlocksFinalForm, createBlockSkeleton, SelectPreviewComponent } from "@comet/blocks-admin";
import * as React from "react";
import { FormattedMessage } from "react-intl";

import { PhoneLinkBlockData, PhoneLinkBlockInput } from "../blocks.generated";
import { isPhoneNumber } from "../validation/isPhoneNumber";
import { validatePhoneNumber } from "../validation/validatePhoneNumber";

export const PhoneLinkBlock: BlockInterface<PhoneLinkBlockData, PhoneLinkBlockData, PhoneLinkBlockInput> = {
...createBlockSkeleton(),

name: "PhoneLink",

displayName: <FormattedMessage id="comet.blocks.link.phone" defaultMessage="Phone Number" />,

defaultValues: () => ({ phone: "" }),

category: BlockCategory.Navigation,

isValid: (state) => {
return state.phone ? isPhoneNumber(state.phone) : true;
},

AdminComponent: ({ state, updateState }) => {
return (
<SelectPreviewComponent>
<BlocksFinalForm onSubmit={updateState} initialValues={state}>
<Field
label={<FormattedMessage id="comet.blocks.link.phone" defaultMessage="Phone Number" />}
name="phone"
component={FinalFormInput}
fullWidth
validate={validatePhoneNumber}
/>
</BlocksFinalForm>
</SelectPreviewComponent>
);
},

previewContent: (state) => {
return state.phone ? [{ type: "text", content: state.phone }] : [];
},
};
2 changes: 2 additions & 0 deletions packages/admin/cms-admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export type { TextImageBlockFactoryOptions } from "./blocks/createTextImageBlock
export { createTextImageBlock } from "./blocks/createTextImageBlock";
export { createTextLinkBlock } from "./blocks/createTextLinkBlock";
export { DamVideoBlock } from "./blocks/DamVideoBlock";
export { EmailLinkBlock } from "./blocks/EmailLinkBlock";
export { ExternalLinkBlock } from "./blocks/ExternalLinkBlock";
export { EditImageDialog } from "./blocks/image/EditImageDialog";
export { InternalLinkBlock } from "./blocks/InternalLinkBlock";
export { PhoneLinkBlock } from "./blocks/PhoneLinkBlock";
export { PixelImageBlock } from "./blocks/PixelImageBlock";
export { SvgImageBlock } from "./blocks/SvgImageBlock";
export { useCmsBlockContext } from "./blocks/useCmsBlockContext";
Expand Down
4 changes: 2 additions & 2 deletions packages/admin/cms-admin/src/validation/isLinkTarget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isEmail, isURL } from "class-validator";

const PHONE_NUMBER_REGEX = /^\+?[0-9\s]+$/;
import { isPhoneNumber } from "./isPhoneNumber";

export function isLinkTarget(value: string): boolean {
if (value.toLowerCase().includes("javascript:")) {
Expand All @@ -10,7 +10,7 @@ export function isLinkTarget(value: string): boolean {
} else if (value.startsWith("mailto:")) {
return isEmail(value.slice(7));
} else if (value.startsWith("tel:")) {
return PHONE_NUMBER_REGEX.test(value.slice(4));
return isPhoneNumber(value.slice(4));
} else {
return isURL(value, { require_protocol: true, require_valid_protocol: false });
}
Expand Down
5 changes: 5 additions & 0 deletions packages/admin/cms-admin/src/validation/isPhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const PHONE_NUMBER_REGEX = /^\+?[0-9\s]+$/;

export function isPhoneNumber(value: string): boolean {
return PHONE_NUMBER_REGEX.test(value);
}
10 changes: 10 additions & 0 deletions packages/admin/cms-admin/src/validation/validatePhoneNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import { FormattedMessage } from "react-intl";

import { isPhoneNumber } from "./isPhoneNumber";

export function validatePhoneNumber(value?: string) {
if (value && !isPhoneNumber(value)) {
return <FormattedMessage id="comet.validation.validatePhoneNumber.invalid" defaultMessage="Invalid phone number" />;
}
}
34 changes: 34 additions & 0 deletions packages/api/blocks-api/block-meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
[
{
"name": "EmailLink",
"fields": [
{
"name": "email",
"kind": "String",
"nullable": true
}
],
"inputFields": [
{
"name": "email",
"kind": "String",
"nullable": true
}
]
},
{
"name": "ExternalLink",
"fields": [
Expand Down Expand Up @@ -26,6 +43,23 @@
}
]
},
{
"name": "PhoneLink",
"fields": [
{
"name": "phone",
"kind": "String",
"nullable": false
}
],
"inputFields": [
{
"name": "phone",
"kind": "String",
"nullable": false
}
]
},
{
"name": "Space",
"fields": [
Expand Down
2 changes: 1 addition & 1 deletion packages/api/blocks-api/src/blocks/ExternalLinkBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IsBoolean, IsOptional } from "class-validator";

import { BlockData, BlockInput, createBlock, inputToData } from "./block";
import { BlockField } from "./decorators/field";
import { IsLinkTarget } from "./externalLinkBlock/is-link-target.validator";
import { IsLinkTarget } from "./validator/is-link-target.validator";

class ExternalLinkBlockData extends BlockData {
@BlockField({ nullable: true })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from "@nestjs/common";
import { isEmail, isString, isURL, registerDecorator, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";

const PHONE_NUMBER_REGEX = /^\+?[0-9\s]+$/;
import { isPhoneNumber } from "./is-phone-number.validator";

export const IsLinkTarget = () => {
// eslint-disable-next-line @typescript-eslint/ban-types
Expand Down Expand Up @@ -29,7 +29,7 @@ export class IsLinkTargetConstraint implements ValidatorConstraintInterface {
} else if (value.startsWith("mailto:")) {
return isEmail(value.slice(7));
} else if (value.startsWith("tel:")) {
return PHONE_NUMBER_REGEX.test(value.slice(4));
return isPhoneNumber(value.slice(4));
} else {
return isURL(value, { require_protocol: true, require_valid_protocol: false });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const PHONE_NUMBER_REGEX = /^\+?[0-9\s]+$/;

export const isPhoneNumber = (value: string): boolean => {
return PHONE_NUMBER_REGEX.test(value);
};
Loading

0 comments on commit 73dfb61

Please sign in to comment.