diff --git a/components/CaseTOC.tsx b/components/CaseTOC.tsx
new file mode 100644
index 00000000000..6008a6ea156
--- /dev/null
+++ b/components/CaseTOC.tsx
@@ -0,0 +1,198 @@
+import { useMemo, useState } from 'react';
+import { twMerge } from 'tailwind-merge';
+
+import { useHeadingsObserver } from './helpers/useHeadingsObserver';
+import ArrowRight from './icons/ArrowRight';
+
+interface TocItem {
+ lvl: number;
+ content: string;
+ slug: string;
+ children?: TocItem[];
+}
+
+interface TOCItemProps {
+ item: TocItem;
+ index: number;
+ currSelected: string;
+ closeMenu: () => void;
+}
+
+interface CaseTOCProps {
+ className: string;
+ cssBreakingPoint?: 'xl' | 'lg';
+ toc: any[];
+}
+
+/**
+ * @description Checks if the item is active.
+ *
+ * @param {TocItem} item - The TOC item to check.
+ * @param {string} currSelected - The currently selected TOC item.
+ * @returns {boolean} - True if the item is active, otherwise false.
+ */
+const checkIfActive = (item: TocItem, currSelected: string): boolean => {
+ return item.slug === currSelected || item.children?.some((child) => checkIfActive(child, currSelected)) || false;
+};
+
+/**
+ * @description Converts content to TOC items.
+ *
+ * @param {any[]} content - The content to convert to TOC items.
+ * @param {number} level - The level of the TOC item.
+ * @returns {TocItem[]} - The array of TOC items.
+ */
+const convertContentToTocItems = (content: any[], level: number = 1): TocItem[] => {
+ const tocItems = [];
+
+ for (const section of content) {
+ const item = {
+ lvl: level,
+ content: section.title,
+ slug: section.title
+ .replace(/<|>|"|\\|\/|=/gi, '')
+ .replace(/\s/gi, '-')
+ .toLowerCase()
+ };
+
+ if (section.children && section.children.length > 0) {
+ const children = convertContentToTocItems(section.children, level + 1);
+
+ (item as TocItem).children = children;
+ }
+
+ tocItems.push(item);
+ }
+
+ return tocItems;
+};
+
+/**
+ * @description Component representing an item in the table of contents (TOC).
+ *
+ * @param {TOCItemProps} props - The props for TOCItem.
+ * @param {TocItem} props.item - The TOC item.
+ * @param {number} props.index - The index of the TOC item.
+ * @param {string} props.currSelected - The currently selected TOC item.
+ * @param {Function} props.closeMenu - A function to close the menu.
+ */
+function TOCItem({ item, index, currSelected, closeMenu }: TOCItemProps) {
+ const [open, setOpen] = useState(false);
+ const active = useMemo(() => checkIfActive(item, currSelected), [item, currSelected]);
+
+ const handleClick = () => {
+ closeMenu();
+ setOpen(false);
+ };
+
+ return (
+ <>
+
+ {item.children && item.children.length > 0 && (
+
+ {item.children.map((child_item, child_index) => (
+
+ ))}
+
+ )}
+ >
+ );
+}
+
+/**
+ * @description Component representing a table of contents (TOC) for a case.
+ *
+ * @param {CaseTOCProps} props - The props for CaseTOC.
+ * @param {string} props.className - The CSS class name for the component.
+ * @param {("xl"|"lg")} [props.cssBreakingPoint="xl"] - The CSS breaking point for responsiveness.
+ * @param {any[]} props.toc - The table of contents data.
+ */
+export default function CaseTOC({ className, cssBreakingPoint = 'xl', toc }: CaseTOCProps) {
+ const { currActive: selected } = useHeadingsObserver();
+ const [open, setOpen] = useState(false);
+ const tocItems = useMemo(() => convertContentToTocItems(toc), [toc]);
+
+ if (!toc || !toc.length) return null;
+
+ return (
+
+
+
+
+ {tocItems.map((item, index) => (
+ setOpen(false)}
+ currSelected={selected || ''}
+ />
+ ))}
+
+
+
+ );
+}
diff --git a/components/GeneratorInstallation.tsx b/components/GeneratorInstallation.tsx
new file mode 100644
index 00000000000..d280a2199e4
--- /dev/null
+++ b/components/GeneratorInstallation.tsx
@@ -0,0 +1,99 @@
+import { useState } from 'react';
+
+import { ParagraphTypeStyle } from '@/types/typography/Paragraph';
+
+import generatorflagList from '../config/generator-flags.json';
+import generatorTemplates from '../config/generator-templates.json';
+import CodeBlock from './editor/CodeBlock';
+import Select from './form/Select';
+import Paragraph from './typography/Paragraph';
+
+interface GeneratorFlagData {
+ flag: string;
+ specPath: string;
+}
+
+interface GeneratorFlags {
+ [key: string]: GeneratorFlagData;
+}
+
+/**
+ * @description This component displays generator installation options.
+ */
+export default function GeneratorInstallation() {
+ const [template, setTemplate] = useState('@asyncapi/html-template');
+ // By default we will have output folder flag so its set here.
+ const [params, setParams] = useState('-o example');
+ const [specPath, setSpecPath] = useState('https://bit.ly/asyncapi');
+
+ const generatorflags = generatorflagList as GeneratorFlags;
+
+ /**
+ * @description Handles the change event when selecting a generator template.
+ * @param {string} templateName - The name of the selected template.
+ */
+ function onChangeTemplate(templateName: string) {
+ setTemplate(templateName);
+ if (templateName && generatorflags[templateName]) {
+ const templateBasedJSON = generatorflags[templateName];
+
+ // options are generated from generator-templates.json
+ // and flags are fetched from generator-flags.json,
+ // so it is mandatory to have check in case if any misses the option in future
+
+ if (templateBasedJSON) {
+ setParams(templateBasedJSON.flag);
+ setSpecPath(templateBasedJSON.specPath);
+ }
+ }
+ }
+
+ /**
+ * @description Generates the npm install command.
+ * @returns {string} The npm install command.
+ */
+ function getNpmCode(): string {
+ return `npm install -g @asyncapi/cli
+asyncapi generate fromTemplate ${specPath} ${template} ${params}`;
+ }
+
+ /**
+ * Generates the Docker command.
+ * @returns {string} The Docker command.
+ */
+ function getDockerCode(): string {
+ return `docker run --rm -it -v \${PWD}/example:/app/example -v \${PWD}/output:/app/output \\
+asyncapi/cli generate fromTemplate ${specPath} ${template} ${params}`;
+ }
+
+ return (
+
+
+
+ Select a Generator template:
+
+
+
+
+
+ );
+}
diff --git a/components/Hero.tsx b/components/Hero.tsx
new file mode 100644
index 00000000000..6ce5289e085
--- /dev/null
+++ b/components/Hero.tsx
@@ -0,0 +1,87 @@
+import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
+import { ParagraphTypeStyle } from '@/types/typography/Paragraph';
+
+import { useTranslation } from '../utils/i18n';
+import AlgoliaSearch, { SearchButton } from './AlgoliaSearch'; // Import AlgoliaSearch component
+import Button from './buttons/Button';
+import AnnouncementHero from './campaigns/AnnouncementHero';
+import DemoAnimation from './DemoAnimation';
+import Features from './features';
+import ArrowRight from './icons/ArrowRight';
+import IconLoupe from './icons/Loupe';
+import Heading from './typography/Heading';
+import Paragraph from './typography/Paragraph';
+
+interface HeroProps {
+ className?: string;
+}
+
+/**
+ * @description This component displays the hero section on the Home page.
+ *
+ * @param {HeroProps} props - The props for Hero Component.
+ * @param {string} props.className - Additional CSS classes for styling.
+ */
+export default function Hero({ className = '' }: HeroProps) {
+ const { t } = useTranslation('landing-page');
+
+ return (
+ <>
+
+
+
+
+ {t('main.header')} {t('main.subHeader')}
+
+
+ {t('main.body_pretext')} {t('main.body_boldtext')}
+ {t('main.body_posttext')}
+
+
+
}
+ data-testid='Hero-Button'
+ />
+ {/* Wrap SearchButton with AlgoliaSearch component */}
+
+
+ {({ actionKey }) => (
+ <>
+
+ {t('main.search_btn')}
+ {actionKey && (
+
+
+ {actionKey.shortKey}
+ {' '}
+ K
+
+ )}
+ >
+ )}
+
+
+
+
+ {t('main.slogan_text')}{' '}
+
+ {t('main.slogan_link')}
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/components/InlineHelp.tsx b/components/InlineHelp.tsx
new file mode 100644
index 00000000000..ec8d039da2a
--- /dev/null
+++ b/components/InlineHelp.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+
+import { registerClickAway } from './helpers/click-away';
+import QuestionMark from './icons/QuestionMark';
+
+interface InlineHelpProps {
+ className?: string;
+ text: string;
+}
+
+/**
+ * @description This component displays Inline Help about outcome. solution, and implementation in the Roadmap page.
+ * @param {InlineHelpProps} props - Props for the InlineHelp component.
+ * @param {string} props.className - Additional CSS classes for styling.
+ * @param {string} props.text - The text to display in the inline help.
+ */
+export default function InlineHelp({ className = 'lg:relative inline-block', text }: InlineHelpProps) {
+ const [isHelpVisible, setIsHelpVisible] = useState(false);
+
+ useEffect(() => {
+ if (isHelpVisible) {
+ registerClickAway(() => {
+ setIsHelpVisible(false);
+ });
+ }
+ }, [isHelpVisible]);
+
+ return (
+
+ {isHelpVisible && (
+
+ {text}
+
+ )}
+
setIsHelpVisible(!isHelpVisible)}
+ onMouseEnter={() => setIsHelpVisible(true)}
+ onMouseLeave={() => setIsHelpVisible(false)}
+ data-testid='InlineHelp-icon'
+ />
+
+ );
+}
diff --git a/components/MDX.tsx b/components/MDX.tsx
new file mode 100644
index 00000000000..04a053c9469
--- /dev/null
+++ b/components/MDX.tsx
@@ -0,0 +1,311 @@
+import { MDXProvider as CoreMDXProvider } from '@mdx-js/react';
+import mermaid from 'mermaid';
+import Link from 'next/link';
+import { useLayoutEffect, useState } from 'react';
+import {
+ TwitterDMButton,
+ TwitterFollowButton,
+ TwitterHashtagButton,
+ TwitterMentionButton,
+ TwitterMomentShare,
+ TwitterOnAirButton,
+ TwitterShareButton,
+ TwitterTimelineEmbed,
+ TwitterTweetEmbed,
+ TwitterVideoEmbed
+} from 'react-twitter-embed';
+import YouTube from 'react-youtube-embed';
+
+import Button from './buttons/Button';
+import ChapterSuggestions from './buttons/ChapterSuggestions';
+import Caption from './Caption';
+import DocsCards from './docs/DocsCards';
+import CodeBlock from './editor/CodeBlock';
+import Figure from './Figure';
+import GeneratorInstallation from './GeneratorInstallation';
+import Column from './layout/Column';
+import Row from './layout/Row';
+import NewsletterSubscribe from './NewsletterSubscribe';
+import Profiles from './Profiles';
+import Remember from './Remember';
+import Sponsors from './sponsors/Sponsors';
+import Warning from './Warning';
+
+let mermaidInitialized = false;
+
+/**
+ * @description Initializes the Mermaid library if not already initialized.
+ */
+function initializeMermaid() {
+ if (mermaidInitialized) {
+ return;
+ }
+
+ mermaidInitialized = true;
+ mermaid.initialize({
+ startOnLoad: true,
+ theme: 'base',
+ securityLevel: 'loose',
+ themeCSS: '',
+ themeVariables: {
+ primaryColor: '#EDFAFF',
+ primaryBorderColor: '#47BCEE',
+ secondaryColor: '#F4EFFC',
+ secondaryBorderColor: '#875AE2',
+ fontFamily: 'Inter, sans-serif',
+ fontSize: '18px',
+ primaryTextColor: '#242929',
+ tertiaryColor: '#F7F9FA',
+ tertiaryBorderColor: '#BFC6C7'
+ }
+ });
+}
+
+initializeMermaid();
+
+let currentId = 0;
+
+/**
+ * @description Generates a unique identifier.
+ * @returns {string} - A unique identifier.
+ */
+const uuid = (): string => `mermaid-${(currentId++).toString()}`;
+
+interface MermaidDiagramProps {
+ graph: string;
+}
+
+/**
+ * @description This component renders Mermaid diagrams.
+ *
+ * @param {MermaidDiagramProps} props - The props for the MermaidDiagram component.
+ * @param {string} props.graph - The Mermaid graph to render.
+ */
+function MermaidDiagram({ graph }: MermaidDiagramProps) {
+ const [svg, setSvg] = useState(null);
+
+ /**
+ * @description Renders the Mermaid diagram.
+ */
+ useLayoutEffect(() => {
+ if (!graph) {
+ return;
+ }
+
+ try {
+ const svgElement = document.createElement('div');
+
+ mermaid.mermaidAPI.render(uuid(), graph, svgElement).then(() => {
+ setSvg(svgElement.innerHTML);
+ });
+ } catch (e) {
+ setSvg(null);
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+ }, [graph]);
+
+ return svg ? : null;
+}
+
+interface CodeComponentProps {
+ children: string;
+ className?: string;
+ metastring?: string;
+ [key: string]: any; // For other props
+}
+
+/**
+ * @description This component renders code blocks.
+ *
+ * @param {CodeComponentProps} props - The props for the CodeComponent component.
+ * @param {string} props.children - The code block content.
+ * @param {string} [props.className] - The code block class name.
+ * @param {string} [props.metastring] - The code block metastring.
+ */
+function CodeComponent({ children, className = '', metastring = '', ...rest }: CodeComponentProps) {
+ let caption;
+ const meta = metastring.split(/([\w]+=[\w\d\s\-_:><.]+)/) || [];
+
+ meta.forEach((str) => {
+ const params = new URLSearchParams(str);
+
+ caption = params.get('caption') || '';
+ if (caption.startsWith("'") && caption.endsWith("'")) {
+ caption = caption.substring(1, caption.length - 1);
+ }
+ });
+ const maybeLanguage = className.match(/language-([\w\d\-_]+)/);
+ const language = maybeLanguage && maybeLanguage.length >= 2 ? maybeLanguage[1] : undefined;
+
+ if (language === 'mermaid') {
+ return ;
+ }
+
+ return (
+ 2}
+ >
+ {children}
+
+ );
+}
+
+/**
+ * @description This function returns MDX components.
+ */
+export function getMDXComponents() {
+ return {
+ h1: (props: React.HTMLProps) => (
+
+ ),
+ h2: (props: React.HTMLProps) => (
+
+ ),
+ h3: (props: React.HTMLProps) => (
+
+ ),
+ h4: (props: React.HTMLProps) => (
+
+ ),
+ h5: (props: React.HTMLProps) => (
+
+ ),
+ h6: (props: React.HTMLProps) => (
+
+ ),
+ blockquote: (props: React.HTMLProps) => (
+
+ ),
+ p: (props: React.HTMLProps) => (
+
+ ),
+ strong: (props: React.HTMLProps) => (
+
+ ),
+ a: (props: React.HTMLProps) => (
+
+ ),
+ ul: (props: React.HTMLProps) => (
+
+ ),
+ ol: (props: React.HTMLProps) => (
+
+ ),
+ li: (props: React.HTMLProps) => (
+
+ ),
+ button: Button as React.ComponentType>,
+ table: (props: React.HTMLProps) => (
+
+ ),
+ th: (props: React.HTMLProps) => (
+ |
+ ),
+ tr: (props: React.HTMLProps) => (
+
+ ),
+ td: (props: React.HTMLProps) => (
+ |
+ ),
+ pre: (props: React.HTMLProps) => CodeComponent(props.children as CodeComponentProps),
+ code: (props: React.HTMLProps) => (
+
+ ),
+ hr: (props: React.HTMLProps) =>
,
+ CodeBlock,
+ ChapterSuggestions,
+ YouTube,
+ Remember,
+ Warning,
+ Sponsors,
+ Caption,
+ Link,
+ Row,
+ Column,
+ Figure,
+ DocsCards,
+ GeneratorInstallation,
+ NewsletterSubscribe,
+ TwitterTimelineEmbed,
+ TwitterShareButton,
+ TwitterFollowButton,
+ TwitterHashtagButton,
+ TwitterMentionButton,
+ TwitterTweetEmbed,
+ TwitterMomentShare,
+ TwitterDMButton,
+ TwitterVideoEmbed,
+ TwitterOnAirButton,
+ Profiles
+ };
+}
+
+const mdxComponents = getMDXComponents();
+
+interface MDXProviderProps {
+ children: React.ReactNode;
+}
+
+/**
+ * @description This component provides MDX components.
+ *
+ * @param {MDXProviderProps} props - The props for the MDXProvider component.
+ * @param {React.ReactNode} props.children - The children to render.
+ */
+export function MDXProvider({ children }: MDXProviderProps) {
+ return {children};
+}
diff --git a/components/NewsletterSubscribe.tsx b/components/NewsletterSubscribe.tsx
new file mode 100644
index 00000000000..669a848fb49
--- /dev/null
+++ b/components/NewsletterSubscribe.tsx
@@ -0,0 +1,161 @@
+import { useState } from 'react';
+
+import { ButtonType } from '@/types/components/buttons/ButtonPropsType';
+import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
+
+import { useTranslation } from '../utils/i18n';
+import Button from './buttons/Button';
+import Loader from './Loader';
+import Heading from './typography/Heading';
+import Paragraph from './typography/Paragraph';
+import TextLink from './typography/TextLink';
+
+enum FormStatus {
+ NORMAL = 'normal',
+ LOADING = 'loading',
+ SUCCESS = 'success',
+ ERROR = 'error'
+}
+
+interface NewsletterSubscribeProps {
+ className?: string;
+ dark?: boolean;
+ title?: string;
+ subtitle?: string;
+ type?: string;
+}
+
+/**
+ * @description This component displays Newsletter Subscribe component.
+ *
+ * @param {NewsletterSubscribeProps} props - The props for the Newsletter Subscribe component.
+ * @param {string} props.className - CSS class for styling the card.
+ * @param {boolean} props.dark - If true, the theme of the component will be dark.
+ * @param {string} props.title - The title of the Subscribe card.
+ * @param {string} props.subtitle - The subtitle of the Subscribe card.
+ * @param {string} props.type - The type of subscription.
+ */
+export default function NewsletterSubscribe({
+ className = 'p-8 text-center',
+ dark = false,
+ title = 'Subscribe to our newsletter to receive news about AsyncAPI.',
+ subtitle = 'We respect your inbox. No spam, promise ✌️',
+ type = 'Newsletter'
+}: NewsletterSubscribeProps) {
+ const [email, setEmail] = useState('');
+ const [name, setName] = useState('');
+ const [status, setStatus] = useState(FormStatus.NORMAL);
+
+ const { t } = useTranslation('common');
+
+ const headTextColor = dark ? 'text-white' : '';
+ const paragraphTextColor = dark ? 'text-gray-300' : '';
+
+ const setFormStatus = (formResponse: FormStatus) => {
+ setStatus(formResponse);
+ setTimeout(() => {
+ setStatus(FormStatus.NORMAL);
+ }, 10000);
+ };
+
+ const handleSubmit = (event: React.FormEvent) => {
+ setStatus(FormStatus.LOADING);
+ event.preventDefault();
+ const data = {
+ name,
+ email,
+ interest: type
+ };
+
+ fetch('/.netlify/functions/newsletter_subscription', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then((res) => {
+ if (res.status === 200) {
+ setFormStatus(FormStatus.SUCCESS);
+ } else {
+ setFormStatus(FormStatus.ERROR);
+ }
+
+ return res.json();
+ })
+ // eslint-disable-next-line @typescript-eslint/no-shadow, no-console
+ .then((data) => console.log(data));
+ };
+
+ if (status === FormStatus.SUCCESS) {
+ return (
+
+
+ {t('newsletterCTA.successTitle')}
+
+
+ {t('newsletterCTA.subtitle')}
+
+
+ );
+ }
+
+ if (status === FormStatus.ERROR) {
+ return (
+
+
+ {t('newsletterCTA.errorTitle')}
+
+
+ {t('newsletterCTA.errorSubtitle')}{' '}
+
+ {t('newsletterCTA.errorLinkText')}
+
+
+
+ );
+ }
+
+ return (
+
+
+ {title}
+
+
+ {subtitle}
+
+ {status === 'loading' ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/components/Testimonial.tsx b/components/Testimonial.tsx
new file mode 100644
index 00000000000..a1dae1c6a28
--- /dev/null
+++ b/components/Testimonial.tsx
@@ -0,0 +1,50 @@
+import Quote from './icons/Quote';
+import Paragraph from './typography/Paragraph';
+
+interface TestimonialProps {
+ className?: string;
+ text: string;
+ authorName: string;
+ authorDescription: string;
+ authorAvatar: string;
+}
+
+/**
+ * @description This component displays Testimonial component.
+ *
+ * @param {TestimonialProps} props - The props for the Testimonial component.
+ * @param {string} props.className - Additional CSS class for styling the card.
+ * @param {string} props.text - The testimonial from the author.
+ * @param {string} props.authorName - The name of the author.
+ * @param {string} props.authorDescription - The description of the author.
+ * @param {string} props.authorAvatar - The path to avatar of the author.
+ */
+export default function Testimonial({
+ className = '',
+ text,
+ authorName,
+ authorDescription,
+ authorAvatar
+}: TestimonialProps) {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/components/editor/CodeBlock.tsx b/components/editor/CodeBlock.tsx
index e38f15e3d16..509b03845c7 100644
--- a/components/editor/CodeBlock.tsx
+++ b/components/editor/CodeBlock.tsx
@@ -5,7 +5,7 @@ import Caption from '../Caption';
import IconClipboard from '../icons/Clipboard';
interface CodeBlockProps {
- children: string;
+ children?: string;
codeBlocks?: { code: string; title?: string; language?: string }[];
className?: string;
highlightClassName?: string;
@@ -196,7 +196,7 @@ const theme: Theme = {
* @param {string} props.title - The title of the code block (default is the specified language).
*/
export default function CodeBlock({
- children,
+ children = '',
codeBlocks,
className = '',
highlightClassName = '',
diff --git a/components/features/FeatureList.ts b/components/features/FeatureList.ts
new file mode 100644
index 00000000000..130cd259952
--- /dev/null
+++ b/components/features/FeatureList.ts
@@ -0,0 +1,88 @@
+interface FeatureLink {
+ label: string;
+ href: string;
+ id: string;
+}
+
+interface Feature {
+ id: string;
+ links: FeatureLink[];
+}
+
+export const features: Feature[] = [
+ {
+ id: 'specification',
+ links: [
+ {
+ label: 'Documentation',
+ href: 'docs/reference/specification/latest',
+ id: 'whyasyncapi-spec-documentation-link'
+ }
+ ]
+ },
+ {
+ id: 'document-apis',
+ links: [
+ {
+ label: 'HTML Template',
+ href: 'https://github.com/asyncapi/html-template',
+ id: 'whyasyncapi-apis-htmltemplate-link'
+ },
+ {
+ label: 'React Component',
+ href: 'https://github.com/asyncapi/asyncapi-react/',
+ id: 'whyasyncapi-apis-reactcomponents-link'
+ }
+ ]
+ },
+ {
+ id: 'code-generation',
+ links: [
+ {
+ label: 'Generator',
+ href: 'tools/generator',
+ id: 'whyasyncapi-generation-generator-link'
+ },
+ {
+ label: 'Modelina',
+ href: 'tools/modelina',
+ id: 'whyasyncapi-generation-modelina-link'
+ }
+ ]
+ },
+ {
+ id: 'community',
+ links: [
+ {
+ label: 'Join our Slack',
+ href: 'https://asyncapi.com/slack-invite',
+ id: 'whyasyncapi-community-slack-link'
+ }
+ ]
+ },
+ {
+ id: 'open-governance',
+ links: [
+ {
+ label: 'Read more about Open Governance',
+ href: 'blog/governance-motivation',
+ id: 'whyasyncapi-governance-more-link'
+ },
+ {
+ label: 'TSC Members',
+ href: 'community/tsc',
+ id: 'whyasyncapi-governance-tsc-link'
+ }
+ ]
+ },
+ {
+ id: 'much-more',
+ links: [
+ {
+ label: 'View GitHub Discussions',
+ href: 'https://github.com/asyncapi/community/discussions',
+ id: 'whyasyncapi-muchmore-github-link'
+ }
+ ]
+ }
+];
diff --git a/components/features/index.tsx b/components/features/index.tsx
new file mode 100644
index 00000000000..7c65fe737df
--- /dev/null
+++ b/components/features/index.tsx
@@ -0,0 +1,60 @@
+import Link from 'next/link';
+import React from 'react';
+
+import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
+import { ParagraphTypeStyle } from '@/types/typography/Paragraph';
+
+import { useTranslation } from '../../utils/i18n';
+import Heading from '../typography/Heading';
+import Paragraph from '../typography/Paragraph';
+import TextLink from '../typography/TextLink';
+import { features } from './FeatureList';
+
+/**
+ * @description This component displays AsyncAPI Features in the Hero section of the Home page.
+ */
+export default function Features() {
+ const { t } = useTranslation('landing-page');
+
+ return (
+
+
+
+ {t('features.title')}
+
+
{t('features.description')}
+
+
+
+ );
+}
diff --git a/components/helpers/click-away.ts b/components/helpers/click-away.ts
new file mode 100644
index 00000000000..39df9b9d68b
--- /dev/null
+++ b/components/helpers/click-away.ts
@@ -0,0 +1,36 @@
+/**
+ * @description Registers a callback function to be invoked when a click occurs outside of specified elements.
+ * @param {function} callback - The callback function to be invoked when a click occurs outside of specified elements.
+ * It takes a MouseEvent parameter and returns void.
+ */
+export function registerClickAway(callback: (event: MouseEvent) => void) {
+ /**
+ * @description Handles the click event and invokes the callback function if the click occurs outside of specified elements.
+ * @param {MouseEvent} event - The MouseEvent object representing the click event.
+ */
+ function unregisterClickAway(event: MouseEvent) {
+ document.removeEventListener('click', unregisterClickAway);
+
+ document.querySelectorAll('iframe').forEach((iframe) => {
+ const src = iframe.attributes.getNamedItem('src');
+
+ if (src && src.value.startsWith('/') && !src.value.startsWith('//')) {
+ iframe.contentWindow?.document.removeEventListener('click', unregisterClickAway);
+ }
+ });
+
+ callback(event);
+ }
+
+ document.removeEventListener('click', unregisterClickAway);
+ document.addEventListener('click', unregisterClickAway);
+
+ document.querySelectorAll('iframe').forEach((iframe) => {
+ const src = iframe.attributes.getNamedItem('src');
+
+ if (src && src.value.startsWith('/') && !src.value.startsWith('//')) {
+ iframe.contentWindow?.document.removeEventListener('click', unregisterClickAway);
+ iframe.contentWindow?.document.addEventListener('click', unregisterClickAway);
+ }
+ });
+}
diff --git a/components/helpers/useHeadingsObserver.tsx b/components/helpers/useHeadingsObserver.tsx
new file mode 100644
index 00000000000..0a340d21633
--- /dev/null
+++ b/components/helpers/useHeadingsObserver.tsx
@@ -0,0 +1,36 @@
+import { useEffect, useRef, useState } from 'react';
+
+/**
+ * @description Custom hook to observe headings and set the current active heading
+ * @example const { currActive } = useHeadingsObserver();
+ * @returns {object} currActive - current active heading
+ */
+export function useHeadingsObserver() {
+ const observer = useRef(null);
+ const headingsRef = useRef | []>([]);
+ const [currActive, setCurrActive] = useState(null);
+
+ useEffect(() => {
+ const callback = (entries: IntersectionObserverEntry[]) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ setCurrActive(entry.target.id);
+ }
+ });
+ };
+
+ // The heading in from top 20% of the viewport to top 30% of the viewport will be considered as active
+ observer.current = new IntersectionObserver(callback, {
+ rootMargin: '-20% 0px -70% 0px'
+ });
+
+ headingsRef.current = document.querySelectorAll('h2, h3');
+ headingsRef.current.forEach((heading) => {
+ observer.current?.observe(heading);
+ });
+
+ return () => observer.current?.disconnect();
+ }, []);
+
+ return { currActive };
+}
diff --git a/components/icons/Quote.tsx b/components/icons/Quote.tsx
new file mode 100644
index 00000000000..aa9f1dd99e5
--- /dev/null
+++ b/components/icons/Quote.tsx
@@ -0,0 +1,11 @@
+/* eslint-disable max-len */
+/**
+ * @description Icons for asyncapi website
+ */
+export default function QuestionMark({ className, ...props }: React.SVGProps) {
+ return (
+
+ );
+}
diff --git a/package-lock.json b/package-lock.json
index 4f9b10ced0a..439e915ae59 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -80,6 +80,7 @@
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/react-text-truncate": "^0.14.4",
+ "@types/react-youtube-embed": "^1.0.4",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"dedent": "^1.5.1",
@@ -2293,6 +2294,15 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-youtube-embed": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-youtube-embed/-/react-youtube-embed-1.0.4.tgz",
+ "integrity": "sha512-vxTETSo8VjsPixBKapFnLVxggDD+0IWk8hXhStVeml3rIaWA5PS4zFPZnINmVWBDdAWT44416Ac6T+vQTYXXkA==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
diff --git a/package.json b/package.json
index 90834a138f1..a583ec70a87 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,7 @@
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/react-text-truncate": "^0.14.4",
+ "@types/react-youtube-embed": "^1.0.4",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"dedent": "^1.5.1",