diff --git a/dotcom-rendering/src/components/ReporterCalloutBlockComponent.importable.tsx b/dotcom-rendering/src/components/ReporterCalloutBlockComponent.importable.tsx
new file mode 100644
index 00000000000..1b872d87057
--- /dev/null
+++ b/dotcom-rendering/src/components/ReporterCalloutBlockComponent.importable.tsx
@@ -0,0 +1,52 @@
+import { isUndefined } from '@guardian/libs';
+import type { ReporterCalloutBlockElement } from '../types/content';
+import { Body } from './ExpandableAtom/Body';
+import { Container } from './ExpandableAtom/Container';
+
+/**
+ * A callout to readers to share tips with reporters
+ *
+ * ## TODO: check if this needs to be an island - possibly not as there's no user interaction beyond expanding the box
+ *
+ */
+
+export const ReporterCalloutBlockComponent = ({
+ callout,
+}: {
+ callout: ReporterCalloutBlockElement;
+}) => {
+ const {
+ id,
+ title,
+ description,
+ activeUntil,
+ // contacts, TODO: Render contacts
+ } = callout;
+ const isExpired = isUndefined(activeUntil)
+ ? false
+ : Math.floor(new Date().getTime() / 1000) > activeUntil;
+
+ return isExpired ? (
+ <>>
+ ) : (
+
+ {
+ // do nothing - I don't think we want to track interactions with this component in ophan
+ }}
+ >
+
+
+
+ );
+};
diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json
index 03ee010d1cd..fa6fffd2ef1 100644
--- a/dotcom-rendering/src/frontend/schemas/feArticle.json
+++ b/dotcom-rendering/src/frontend/schemas/feArticle.json
@@ -611,6 +611,9 @@
{
"$ref": "#/definitions/CalloutBlockElementV2"
},
+ {
+ "$ref": "#/definitions/ReporterCalloutBlockElement"
+ },
{
"$ref": "#/definitions/CartoonBlockElement"
},
@@ -1547,6 +1550,53 @@
"value"
]
},
+ "ReporterCalloutBlockElement": {
+ "type": "object",
+ "properties": {
+ "_type": {
+ "type": "string",
+ "const": "model.dotcomrendering.pageElements.ReporterCalloutBlockElement"
+ },
+ "elementId": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "activeFrom": {
+ "type": "number"
+ },
+ "activeUntil": {
+ "type": "number"
+ },
+ "displayOnSensitive": {
+ "type": "boolean"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "role": {
+ "$ref": "#/definitions/RoleType"
+ },
+ "contacts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/CalloutContactType"
+ }
+ }
+ },
+ "required": [
+ "_type",
+ "description",
+ "displayOnSensitive",
+ "elementId",
+ "id",
+ "title"
+ ]
+ },
"CartoonBlockElement": {
"type": "object",
"properties": {
diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx
index 5d28c63b295..0a12cc61fa0 100644
--- a/dotcom-rendering/src/lib/renderElement.tsx
+++ b/dotcom-rendering/src/lib/renderElement.tsx
@@ -42,6 +42,7 @@ import { ProfileAtomWrapper } from '../components/ProfileAtomWrapper.importable'
import { PullQuoteBlockComponent } from '../components/PullQuoteBlockComponent';
import { QandaAtom } from '../components/QandaAtom.importable';
import { QAndAExplainers } from '../components/QAndAExplainers';
+import { ReporterCalloutBlockComponent } from '../components/ReporterCalloutBlockComponent.importable';
import { RichLinkComponent } from '../components/RichLinkComponent.importable';
import { SoundcloudBlockComponent } from '../components/SoundcloudBlockComponent';
import { SpotifyBlockComponent } from '../components/SpotifyBlockComponent.importable';
@@ -220,6 +221,15 @@ export const renderElement = ({
);
}
return null;
+ case 'model.dotcomrendering.pageElements.ReporterCalloutBlockElement':
+ if (switches.callouts) {
+ return (
+
+
+
+ );
+ }
+ return null;
case 'model.dotcomrendering.pageElements.CaptionBlockElement':
return (
diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json
index 776b641d0c7..a5d68a872c8 100644
--- a/dotcom-rendering/src/model/block-schema.json
+++ b/dotcom-rendering/src/model/block-schema.json
@@ -99,6 +99,9 @@
{
"$ref": "#/definitions/CalloutBlockElementV2"
},
+ {
+ "$ref": "#/definitions/ReporterCalloutBlockElement"
+ },
{
"$ref": "#/definitions/CartoonBlockElement"
},
@@ -1035,6 +1038,53 @@
"value"
]
},
+ "ReporterCalloutBlockElement": {
+ "type": "object",
+ "properties": {
+ "_type": {
+ "type": "string",
+ "const": "model.dotcomrendering.pageElements.ReporterCalloutBlockElement"
+ },
+ "elementId": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "activeFrom": {
+ "type": "number"
+ },
+ "activeUntil": {
+ "type": "number"
+ },
+ "displayOnSensitive": {
+ "type": "boolean"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "role": {
+ "$ref": "#/definitions/RoleType"
+ },
+ "contacts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/CalloutContactType"
+ }
+ }
+ },
+ "required": [
+ "_type",
+ "description",
+ "displayOnSensitive",
+ "elementId",
+ "id",
+ "title"
+ ]
+ },
"CartoonBlockElement": {
"type": "object",
"properties": {
diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts
index 37b0274f15c..057400978c7 100644
--- a/dotcom-rendering/src/types/content.ts
+++ b/dotcom-rendering/src/types/content.ts
@@ -111,6 +111,19 @@ export interface CalloutBlockElementV2 {
contacts?: CalloutContactType[];
}
+export interface ReporterCalloutBlockElement {
+ _type: 'model.dotcomrendering.pageElements.ReporterCalloutBlockElement';
+ elementId: string;
+ id: string;
+ activeFrom?: number;
+ activeUntil?: number;
+ displayOnSensitive: boolean;
+ title: string;
+ description: string;
+ role?: RoleType;
+ contacts?: CalloutContactType[];
+}
+
export interface CartoonBlockElement {
_type: 'model.dotcomrendering.pageElements.CartoonBlockElement';
elementId: string;
@@ -796,6 +809,7 @@ export type FEElement =
| CaptionBlockElement
| CalloutBlockElement
| CalloutBlockElementV2
+ | ReporterCalloutBlockElement
| CartoonBlockElement
| ChartAtomBlockElement
| CodeBlockElement