diff --git a/apps/roboshield/payload-types.ts b/apps/roboshield/payload-types.ts index 0251bfb07..f78ea8dbd 100644 --- a/apps/roboshield/payload-types.ts +++ b/apps/roboshield/payload-types.ts @@ -47,13 +47,137 @@ export interface Page { fullTitle?: string | null; slug?: string | null; blocks?: - | { - title: string; - subtitle: string; - id?: string | null; - blockName?: string | null; - blockType: "page-header"; - }[] + | ( + | { + title: string; + subtitle: string; + id?: string | null; + blockName?: string | null; + blockType: "page-header"; + } + | { + toolTipText: string; + steps?: + | ( + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + defaultFetchExistingRobots?: boolean | null; + existingRobotsTxt: string; + placeholder: string; + urlValidationError: string; + fetch: string; + id?: string | null; + blockName?: string | null; + blockType: "existing-robots"; + } + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + crawlDelay: { + label: string; + title: string; + }; + cacheDelay: { + label: string; + title: string; + }; + visitTime: { + label: string; + title: string; + }; + id?: string | null; + blockName?: string | null; + blockType: "delays"; + } + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + selectPlatform: { + label: string; + title: string; + }; + disallowedPaths: { + label: string; + title: string; + }; + allowedPaths: { + label: string; + title: string; + }; + id?: string | null; + blockName?: string | null; + blockType: "paths"; + } + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + aiWebCrawlers: { + label: string; + title: string; + }; + searchEngineCrawlers: { + label: string; + title: string; + }; + id?: string | null; + blockName?: string | null; + blockType: "block-bots"; + } + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + placeholder: string; + id?: string | null; + blockName?: string | null; + blockType: "site-maps"; + } + | { + title: string; + hint?: + | { + [k: string]: unknown; + }[] + | null; + placeholder: string; + id?: string | null; + blockName?: string | null; + blockType: "finish"; + } + )[] + | null; + labels: { + continue: string; + back: string; + reset: string; + download: string; + copyToClipboard: string; + }; + id?: string | null; + blockName?: string | null; + blockType: "robo-form"; + } + )[] | null; meta?: { title?: string | null; diff --git a/apps/roboshield/src/components/BlockRenderer/BlockRenderer.tsx b/apps/roboshield/src/components/BlockRenderer/BlockRenderer.tsx index 759cf72b4..60d6aaff0 100644 --- a/apps/roboshield/src/components/BlockRenderer/BlockRenderer.tsx +++ b/apps/roboshield/src/components/BlockRenderer/BlockRenderer.tsx @@ -1,17 +1,20 @@ import PageHeader from "@/roboshield/components/PageHeader/PageHeader"; import { Page } from "@/root/payload-types"; +import RoboForm from "@/roboshield/components/RoboForm"; +import { FC } from "react"; interface BlockRendererProps extends Pick {} const components = { "page-header": PageHeader, + "robo-form": RoboForm, }; export default function BlockRenderer({ blocks }: BlockRendererProps) { return ( <> {blocks?.map((block, index) => { - const Component = components[block.blockType]; + const Component: FC = components[block.blockType]; if (Component) { return ; diff --git a/apps/roboshield/src/components/Code/Code.tsx b/apps/roboshield/src/components/Code/Code.tsx index 13f3d6563..b72adbd2e 100644 --- a/apps/roboshield/src/components/Code/Code.tsx +++ b/apps/roboshield/src/components/Code/Code.tsx @@ -10,6 +10,7 @@ interface CodeProps { onReset: () => void; onBack: () => void; showButtons?: boolean; + labels?: { [key: string]: any }; } export default function Code(props: CodeProps) { @@ -21,6 +22,7 @@ export default function Code(props: CodeProps) { onCodeChange, onBack, showButtons = false, + labels, } = props; const handleCodeChange = (newCode: string) => { @@ -49,7 +51,7 @@ export default function Code(props: CodeProps) { onClick={onCopy} disabled={!showButtons} > - Copy to Clipboard + {labels?.copyToClipboard} @@ -104,7 +106,7 @@ export default function Code(props: CodeProps) { onClick={onReset} disabled={!showButtons} > - Reset + {labels?.reset} diff --git a/apps/roboshield/src/components/CommonBots/CommonBots.tsx b/apps/roboshield/src/components/CommonBots/CommonBots.tsx index 0f17e0933..fa2720c42 100644 --- a/apps/roboshield/src/components/CommonBots/CommonBots.tsx +++ b/apps/roboshield/src/components/CommonBots/CommonBots.tsx @@ -36,6 +36,8 @@ export default function CommonBots({ handleSkipToLast, hint, lastStep, + globalLabels, + toolTipText, }: StepComponent) { const { state } = useGlobalState(); @@ -83,7 +85,11 @@ export default function CommonBots({ return ( <> - + { @@ -217,6 +223,7 @@ export default function CommonBots({ isValid={true} lastStep={lastStep} back={false} + labels={globalLabels} /> ); diff --git a/apps/roboshield/src/components/CommonSettings/CommonSettings.tsx b/apps/roboshield/src/components/CommonSettings/CommonSettings.tsx index 0ef0caf47..e282c3e51 100644 --- a/apps/roboshield/src/components/CommonSettings/CommonSettings.tsx +++ b/apps/roboshield/src/components/CommonSettings/CommonSettings.tsx @@ -19,13 +19,29 @@ import { StepComponent } from "@/roboshield/types/stepComponent"; import SkipToLastStep from "@/roboshield/components/SkipToLastStep"; import StepHint from "@/roboshield/components/StepHint"; +interface LabelNode { + title: string; + label: string; +} + +interface Props extends StepComponent { + selectPlatform?: LabelNode; + allowedPaths?: LabelNode; + disallowedPaths?: LabelNode; +} + export default function CommonSettings({ handleNext, handleBack, handleSkipToLast, hint, lastStep, -}: StepComponent) { + globalLabels, + selectPlatform, + allowedPaths: allowedPathsLabel, + disallowedPaths: disallowedPathsLabel, + toolTipText, +}: Props) { const { state } = useGlobalState(); const [disallowedPaths, setDisallowedPaths] = useState( @@ -78,7 +94,11 @@ export default function CommonSettings({ return ( <> - + - Select platform - + {selectPlatform?.label} + @@ -132,8 +152,8 @@ export default function CommonSettings({ width: "100%", }} > - Disallowed paths - + {disallowedPathsLabel?.label} + @@ -167,8 +187,8 @@ export default function CommonSettings({ width: "100%", }} > - Allowed paths - + {allowedPathsLabel?.label} + @@ -201,6 +221,7 @@ export default function CommonSettings({ isValid={true} lastStep={lastStep} back={false} + labels={globalLabels} /> ); diff --git a/apps/roboshield/src/components/Delays/Delays.tsx b/apps/roboshield/src/components/Delays/Delays.tsx index bc4606bf3..2bd0d7572 100644 --- a/apps/roboshield/src/components/Delays/Delays.tsx +++ b/apps/roboshield/src/components/Delays/Delays.tsx @@ -11,13 +11,28 @@ import { StepComponent } from "@/roboshield/types/stepComponent"; import SkipToLastStep from "@/roboshield/components/SkipToLastStep"; import StepHint from "@/roboshield/components/StepHint"; +interface LabelNode { + title: string; + label: string; +} + +interface Props extends StepComponent { + crawlDelay?: LabelNode; + cacheDelay?: LabelNode; + visitTime?: LabelNode; +} export default function Delays({ handleNext, handleBack, handleSkipToLast, hint, lastStep, -}: StepComponent) { + globalLabels, + cacheDelay: cachedDelayLabel, + crawlDelay: crawlDelayLabel, + visitTime: visitTimeLabel, + toolTipText, +}: Props) { const { state } = useGlobalState(); const [crawlDelay, setCrawlDelay] = useState(state.crawlDelay); @@ -67,7 +82,11 @@ export default function Delays({ return ( <> - + - Crawl delay - + {crawlDelayLabel?.label} + @@ -114,8 +133,8 @@ export default function Delays({ width: "100%", }} > - Cache delay - + {cachedDelayLabel?.label} + @@ -136,8 +155,8 @@ export default function Delays({ width: "100%", }} > - Visit time - + {visitTimeLabel?.label} + @@ -172,6 +191,7 @@ export default function Delays({ isValid={true} lastStep={lastStep} back={false} + labels={globalLabels} /> ); diff --git a/apps/roboshield/src/components/ExistingRobots/ExistingRobots.tsx b/apps/roboshield/src/components/ExistingRobots/ExistingRobots.tsx index 94255baee..b842fcc6c 100644 --- a/apps/roboshield/src/components/ExistingRobots/ExistingRobots.tsx +++ b/apps/roboshield/src/components/ExistingRobots/ExistingRobots.tsx @@ -13,18 +13,32 @@ import { validateUrl } from "@/roboshield/utils/urls"; import SkipToLastStep from "@/roboshield/components/SkipToLastStep"; import StepHint from "@/roboshield/components/StepHint"; +interface Props extends StepComponent { + existingRobotsTxt?: string; + placeholder?: string; + fetch?: string; + urlValidationError?: string; + defaultFetchExistingRobots?: boolean; +} export default function ExistingRobots({ + existingRobotsTxt, + fetch: fetchLabel, + globalLabels, handleNext, handleBack, handleSkipToLast, hint, lastStep, -}: StepComponent) { + placeholder, + urlValidationError, + defaultFetchExistingRobots, + toolTipText, +}: Props) { const { state } = useGlobalState(); const [url, setUrl] = useState(state.url); const [isValid, setIsValid] = useState(false); const [showURLError, setShowURLError] = useState(false); - const [shouldFetch, setShouldFetch] = useState(state.shouldFetch); + const [shouldFetch, setShouldFetch] = useState(defaultFetchExistingRobots); const [robots, setRobots] = useState(state.robots); const [allowNextStep, setAllowNextStep] = useState(false); const [robotsError, setRobotsError] = useState(false); @@ -85,7 +99,11 @@ export default function ExistingRobots({ return ( <> - + } - label={Fetch existing robots.txt} + label={{existingRobotsTxt}} /> - Fetch + {fetchLabel} {showURLError && ( @@ -142,8 +160,7 @@ export default function ExistingRobots({ }, }} > - Please enter a valid URL. A valid URL should start with http:// or - https:// + {urlValidationError} )} @@ -153,6 +170,7 @@ export default function ExistingRobots({ isValid={allowNextStep || !shouldFetch} lastStep={lastStep} back={true} + labels={globalLabels} /> void; + placeholder?: string; +} export default function Finish({ handleReset, handleBack, hint, lastStep, -}: StepComponent & { handleReset: () => void }) { + globalLabels, + toolTipText, +}: Props) { const { state } = useGlobalState(); const [code, setCode] = useState(state.robots || ""); const [showSnackbar, setShowSnackbar] = useState(false); @@ -71,7 +77,11 @@ export default function Finish({ return ( <> - {}} lastStep={lastStep} /> + {}} + lastStep={lastStep} + toolTipText={toolTipText} + /> ; -export default function PageHeader({ title, subtitle }: PageHeaderProps) { +export default function PageHeader({ + title, + subtitle, +}: PageHeaderProps): JSX.Element { return ( <> diff --git a/apps/roboshield/src/components/RoboForm/RoboForm.tsx b/apps/roboshield/src/components/RoboForm/RoboForm.tsx new file mode 100644 index 000000000..8b0ab0b9d --- /dev/null +++ b/apps/roboshield/src/components/RoboForm/RoboForm.tsx @@ -0,0 +1,200 @@ +import { Section } from "@commons-ui/core"; +import { Box, Paper, Stack, Step, StepButton, Stepper } from "@mui/material"; +import { FC, ReactNode, useEffect } from "react"; +import React from "react"; +import { useState } from "react"; + +import Delays from "@/roboshield/components/Delays"; +import Sitemaps from "@/roboshield/components/Sitemaps"; + +import CommonBots from "@/roboshield/components/CommonBots"; +import CommonSettings from "@/roboshield/components/CommonSettings"; +import ExistingRobots from "@/roboshield/components/ExistingRobots"; +import Finish from "@/roboshield/components/Finish"; +import { + useGlobalState, + defaultState, +} from "@/roboshield/context/GlobalContext"; +import { generateRobots } from "@/roboshield/lib/robots"; +import RichText, { Children } from "@/roboshield/components/RichText"; + +type Props = { [key: string]: string } & { + steps: { + title: string; + hint?: Children; + }[]; + labels: { + back: string; + continue: string; + copyToClipboard: string; + download: string; + fetch: string; + reset: string; + }; + toolTipText: string; +}; +const RoboForm: FC = React.forwardRef(function RoboForm(props, ref) { + const { steps, labels, toolTipText } = props; + const [activeStep, setActiveStep] = useState(0); + const { state, setState } = useGlobalState(); + const [code, setCode] = useState(state.robots || ""); + + const stepTitleComponentMap = [ + ExistingRobots, + Delays, + CommonSettings, + CommonBots, + Sitemaps, + Finish, + ]; + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleReset = () => { + setState(defaultState); + setActiveStep(0); + }; + + const handleNextStep = (data: any) => { + const newState = { ...state, ...data }; + setState(newState); + handleNext(); + }; + + const handleSkipToLast = (data: any) => { + const newState = { ...state, ...data }; + setState(newState); + setActiveStep(props.steps.length - 1); + }; + + const handleStep = (step: number) => () => { + setActiveStep(step); + }; + + useEffect(() => { + const generateRobotsFile = async () => { + const robots = await generateRobots(state); + setCode(robots); + }; + + generateRobotsFile(); + }, [state]); + + const ActiveComponent = stepTitleComponentMap[activeStep] ?? null; + const { hint, ...activeComponentProps } = steps[activeStep] ?? {}; + return ( + <> +
+ + + + + + + + {steps?.map((step, index) => ( + + + {step?.title} + + + ))} + + {ActiveComponent && ( + + } + handleNext={handleNextStep} + handleBack={handleBack} + handleSkipToLast={handleSkipToLast} + lastStep={activeStep === steps.length - 1} + handleReset={handleReset} + globalLabels={labels} + toolTipText={toolTipText} + {...activeComponentProps} + /> + + )} + + + + +
+ + ); +}); + +export default RoboForm; diff --git a/apps/roboshield/src/components/RoboForm/index.ts b/apps/roboshield/src/components/RoboForm/index.ts new file mode 100644 index 000000000..0f913a76d --- /dev/null +++ b/apps/roboshield/src/components/RoboForm/index.ts @@ -0,0 +1,3 @@ +import RoboForm from "./RoboForm"; + +export default RoboForm; diff --git a/apps/roboshield/src/components/Sitemaps/Sitemaps.tsx b/apps/roboshield/src/components/Sitemaps/Sitemaps.tsx index 83dca5e70..cf0022e2a 100644 --- a/apps/roboshield/src/components/Sitemaps/Sitemaps.tsx +++ b/apps/roboshield/src/components/Sitemaps/Sitemaps.tsx @@ -8,13 +8,19 @@ import { StepComponent } from "@/roboshield/types/stepComponent"; import SkipToLastStep from "@/roboshield/components/SkipToLastStep"; import StepHint from "@/roboshield/components/StepHint"; +interface Props extends StepComponent { + placeholder?: string; +} export default function Sitemaps({ handleNext, handleBack, handleSkipToLast, hint, lastStep, -}: StepComponent) { + globalLabels, + placeholder, + toolTipText, +}: Props) { const { state } = useGlobalState(); const [sitemaps, setSitemaps] = useState(state.sitemaps); @@ -37,7 +43,11 @@ export default function Sitemaps({ return ( <> - + ); diff --git a/apps/roboshield/src/components/SkipToLastStep/SkipToLastStep.tsx b/apps/roboshield/src/components/SkipToLastStep/SkipToLastStep.tsx index e6dd051b9..ecada2e94 100644 --- a/apps/roboshield/src/components/SkipToLastStep/SkipToLastStep.tsx +++ b/apps/roboshield/src/components/SkipToLastStep/SkipToLastStep.tsx @@ -5,11 +5,13 @@ import CodeIcon from "@mui/icons-material/Code"; interface SkipToLastStepProps { handleSkipToLast: () => void; lastStep: boolean; + toolTipText: string; } const SkipToLastStep = ({ handleSkipToLast, lastStep, + toolTipText, }: SkipToLastStepProps) => { return ( - + { diff --git a/apps/roboshield/src/components/StepperNav/StepperNav.tsx b/apps/roboshield/src/components/StepperNav/StepperNav.tsx index 2eff398ee..c1e6b1176 100644 --- a/apps/roboshield/src/components/StepperNav/StepperNav.tsx +++ b/apps/roboshield/src/components/StepperNav/StepperNav.tsx @@ -6,6 +6,7 @@ interface StepperNavProps { isValid: boolean; lastStep: boolean; back?: boolean; + labels?: { [key: string]: string }; } export default function StepperNav({ @@ -14,6 +15,7 @@ export default function StepperNav({ isValid, lastStep, back = false, + labels, }: StepperNavProps) { return ( @@ -26,10 +28,10 @@ export default function StepperNav({ sx={{ mt: 1, mr: 1 }} disabled={!isValid} > - Continue + {labels?.continue} )} diff --git a/apps/roboshield/src/lib/data/blockify/index.ts b/apps/roboshield/src/lib/data/blockify/index.ts index 6b18ff40c..96454043e 100644 --- a/apps/roboshield/src/lib/data/blockify/index.ts +++ b/apps/roboshield/src/lib/data/blockify/index.ts @@ -1,6 +1,7 @@ import { Page } from "@/root/payload-types"; import { ExtractBlockType } from "@/roboshield/utils/blocks"; import { Api } from "@/roboshield/lib/payload"; +import processBlockRoboForm from "./processBlockRoboForm"; type PropsifyBlockFunction = ( block: T, @@ -15,9 +16,11 @@ type PropsifyBlockBySlug = { >; }; -const pageHeader: PropsifyBlockFunction< - ExtractBlockType[number], "page-header"> -> = async (block, api) => { +type BlockType = ExtractBlockType< + NonNullable[number], + "page-header" +>; +const pageHeader: PropsifyBlockFunction = async (block, api) => { // some block specific computation, i.e using api return { ...block, @@ -27,6 +30,7 @@ const pageHeader: PropsifyBlockFunction< const propsifyBlockBySlug: PropsifyBlockBySlug = { "page-header": pageHeader, + "robo-form": processBlockRoboForm, }; export const blockify = async (blocks: Page["blocks"], api: Api) => { @@ -34,7 +38,7 @@ export const blockify = async (blocks: Page["blocks"], api: Api) => { const slug = block.blockType as NonNullable< Page["blocks"] >[number]["blockType"]; - const propsifyBlock = propsifyBlockBySlug[slug]; + const propsifyBlock = propsifyBlockBySlug[slug] as any; if (propsifyBlock) { return propsifyBlock(block, api); diff --git a/apps/roboshield/src/lib/data/blockify/processBlockRoboForm.ts b/apps/roboshield/src/lib/data/blockify/processBlockRoboForm.ts new file mode 100644 index 000000000..9acc1e5d6 --- /dev/null +++ b/apps/roboshield/src/lib/data/blockify/processBlockRoboForm.ts @@ -0,0 +1,36 @@ +import { Page } from "@/root/payload-types"; +import { Api } from "@/roboshield/lib/payload"; +import { ExtractBlockType } from "@/roboshield/utils/blocks"; + +type PropsifyBlockFunction = ( + block: T, + api: Api, +) => Promise; + +function sortSteps(steps: any[]) { + const stepOrder: string[] = [ + "existing-robots", + "delays", + "paths", + "block-bots", + "site-maps", + "finish", + ]; + return stepOrder.map((slug) => + steps.find(({ blockType }) => blockType === slug), + ); +} + +const processBlockRoboForm: PropsifyBlockFunction< + ExtractBlockType[number], "robo-form"> +> = async (block, api) => { + const steps = sortSteps(block.steps ?? []); + + return { + ...block, + slug: "robo-form", + steps, + }; +}; + +export default processBlockRoboForm; diff --git a/apps/roboshield/src/lib/data/common/index.ts b/apps/roboshield/src/lib/data/common/index.ts index d5e19abc9..85fd15142 100644 --- a/apps/roboshield/src/lib/data/common/index.ts +++ b/apps/roboshield/src/lib/data/common/index.ts @@ -70,7 +70,6 @@ export async function getPageProps( const siteSettings = await api.findGlobal("settings-site"); const navbar = getNavBar(siteSettings); const footer = getFooter(siteSettings); - return { blocks, footer, diff --git a/apps/roboshield/src/payload/Blocks/RoboForm.ts b/apps/roboshield/src/payload/Blocks/RoboForm.ts new file mode 100644 index 000000000..75d38dbf0 --- /dev/null +++ b/apps/roboshield/src/payload/Blocks/RoboForm.ts @@ -0,0 +1,433 @@ +import { Block, Field } from "payload/types"; +import richText from "../fields/richText"; +import { blocks } from "payload/dist/fields/validations"; + +const ExistingRobots: Block = { + slug: "existing-robots", + labels: { singular: "Existing Robots", plural: "Existing Robots" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Existing Robots", + }, + richText({ + name: "hint", + label: "Hint", + defaultValue: [ + { + children: [ + { + text: "Start by fetching the robots.txt file of the website you want to generate robots for.", + }, + ], + }, + ], + }) as unknown as Field, + { + name: "defaultFetchExistingRobots", + type: "checkbox", + label: "Fetch Existing Robots", + defaultValue: false, + }, + { + name: "existingRobotsTxt", + type: "text", + required: true, + defaultValue: "Fetch existing robots.txt", + }, + { + name: "placeholder", + type: "text", + required: true, + defaultValue: "Enter site URL e.g https://example.com", + }, + { + name: "urlValidationError", + type: "text", + required: true, + defaultValue: + "Please enter a valid URL. A valid URL should start with http:// or \nhttps://", + }, + { + name: "fetch", + type: "text", + required: true, + defaultValue: "Fetch", + }, + ], +}; + +const Delays: Block = { + slug: "delays", + labels: { singular: "Delays", plural: "Delays" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Delays", + }, + richText({ + name: "hint", + label: "Hint", + defaultValue: [ + { + children: [ + { + text: "You can set bot delays for the robots you want to generate.", + }, + ], + }, + ], + }) as unknown as Field, + { + name: "crawlDelay", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Crawl delay", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "The crawl delay directive specifies the minimum time between requests to your server from a bot.", + }, + ], + }, + { + name: "cacheDelay", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Cache delay", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "The cache delay directive specifies the time that a cached copy of a page should be considered fresh.", + }, + ], + }, + { + name: "visitTime", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Visit time", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "The visit time directive specifies the time of day when a bot should visit your site.", + }, + ], + }, + ], +}; + +const Paths: Block = { + slug: "paths", + labels: { singular: "Paths", plural: "Paths" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Paths", + }, + richText({ + name: "hint", + label: "hint", + defaultValue: [ + { + children: [ + { + text: "You can set disallowed and allowed paths for the robots you want to generate. All paths should be relative to the root of your site and end with a /", + }, + ], + }, + ], + }) as unknown as Field, + { + name: "selectPlatform", + label: "Select Platform", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Select platform", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "Select the platform your website is built on to generate the correct robots.txt file.", + }, + ], + }, + { + name: "disallowedPaths", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Disallowed paths", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "The disallowed paths directive specifies the paths that a bot should not visit.", + }, + ], + }, + { + name: "allowedPaths", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Allowed Paths", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "The allowed paths directive specifies the paths that a bot should visit.", + }, + ], + }, + ], +}; + +const BlockBots: Block = { + slug: "block-bots", + labels: { singular: "Block Bots", plural: "Block Bots" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Block Bots", + }, + richText({ + name: "hint", + defaultValue: [ + { + children: [ + { + text: "Select bots you want to block from crawling your website.", + }, + ], + }, + ], + }) as unknown as Field, + + { + name: "aiWebCrawlers", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "AI Web Crawlers", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "AI Web Crawlers are automated programs, that systematically browse the internet, indexing content for search engines and retrieving data from web pages. The information they gather can be used to train different models. You might want to block them if you're concerned about attribution or how your creative work could be used in the resulting AI model", + }, + ], + }, + { + name: "searchEngineCrawlers", + type: "group", + fields: [ + { + name: "label", + type: "text", + required: true, + defaultValue: "Search Engine Crawlers", + }, + { + name: "title", + type: "text", + required: true, + defaultValue: + "Search engine crawlers are used to index web content for search engines. Blocking them could prevent your website from being discovered by users.", + }, + ], + }, + ], +}; + +const SiteMaps: Block = { + slug: "site-maps", + labels: { singular: "Site Maps", plural: "Site Maps" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Site Maps", + }, + richText({ + name: "hint", + defaultValue: [ + { + children: [ + { text: "You can add sitemap URLs to your robots.txt file." }, + ], + }, + ], + }) as unknown as Field, + { + name: "placeholder", + type: "text", + required: true, + defaultValue: "Enter sitemap URLs each URL on a new line", + }, + ], +}; + +const Finish: Block = { + slug: "finish", + labels: { singular: "Finish", plural: "Finish" }, + fields: [ + { + name: "title", + type: "text", + required: true, + defaultValue: "Finish", + }, + richText({ + name: "hint", + defaultValue: [ + { + children: [ + { + text: "Your robots.txt file has been generated successfully. You can now copy the code or download the file.", + }, + ], + }, + ], + }) as unknown as Field, + { + name: "placeholder", + type: "text", + required: true, + defaultValue: + "# Disallow specifies the paths that are not allowed to be crawled by the robot.", + }, + ], +}; + +const Labels: Field = { + name: "labels", + label: "Labels", + type: "group", + fields: [ + { + name: "continue", + type: "text", + required: true, + defaultValue: "Continue", + }, + { + name: "back", + type: "text", + required: true, + defaultValue: "Back", + }, + { + name: "reset", + type: "text", + required: true, + defaultValue: "Reset", + }, + { + name: "download", + type: "text", + required: true, + defaultValue: "Download", + }, + { + name: "copyToClipboard", + type: "text", + required: true, + defaultValue: "Copy to Clipboard", + }, + ], +}; +const RoboForm: Block = { + slug: "robo-form", + labels: { singular: "Robo Form", plural: "Robo Form" }, + fields: [ + { + name: "toolTipText", + type: "text", + required: true, + defaultValue: "View current robots.txt file", + }, + { + type: "blocks", + name: "steps", + blocks: [ExistingRobots, Delays, Paths, BlockBots, SiteMaps, Finish], + admin: { + initCollapsed: true, + }, + validate: (value, args) => { + const requiredSteps: string[] = [ + "existing-robots", + "delays", + "paths", + "block-bots", + "site-maps", + "finish", + ]; + const missingSteps = requiredSteps.filter( + (slug) => + !value?.find( + ({ blockType }: { blockType: string }) => blockType === slug, + ), + ); + if (missingSteps.length) { + return `The following steps are missing: ${missingSteps.join(", ")}`; + } + return blocks(value, args); + }, + }, + Labels, + ], +}; + +export default RoboForm; diff --git a/apps/roboshield/src/payload/collections/Pages.ts b/apps/roboshield/src/payload/collections/Pages.ts index 50c6029dd..28eab0f2d 100644 --- a/apps/roboshield/src/payload/collections/Pages.ts +++ b/apps/roboshield/src/payload/collections/Pages.ts @@ -1,6 +1,7 @@ import { CollectionConfig } from "payload/types"; import fullTitle from "../fields/fullTitle"; import slug from "../fields/slug"; +import RoboForm from "../blocks/RoboForm"; import { PageHeader } from "../blocks/PageHeader"; const Pages: CollectionConfig = { @@ -26,7 +27,7 @@ const Pages: CollectionConfig = { { name: "blocks", type: "blocks", - blocks: [PageHeader], + blocks: [PageHeader, RoboForm], localized: true, admin: { initCollapsed: true, diff --git a/apps/roboshield/src/types/stepComponent.ts b/apps/roboshield/src/types/stepComponent.ts index 91944e6a8..bc76f71ab 100644 --- a/apps/roboshield/src/types/stepComponent.ts +++ b/apps/roboshield/src/types/stepComponent.ts @@ -1,7 +1,12 @@ +import { ReactNode } from "react"; + export interface StepComponent { handleNext: (data: any) => void; handleSkipToLast: (data: any) => void; handleBack: () => void; - hint: String; + hint: ReactNode; lastStep: boolean; + labels?: { [key: string]: any }; + globalLabels?: { [key: string]: any }; + toolTipText: string; }