diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000000..5f2ddb01b1eb --- /dev/null +++ b/.eslintrc @@ -0,0 +1,303 @@ +{ + "extends": [ + "airbnb-base", + "next/core-web-vitals", + "eslint:recommended", + "plugin:prettier/recommended" + ], + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "plugins": [ + "react", + "jsx-a11y" + ], + "rules": { + "prettier/prettier": [ + "error", + { + "singleQuote": true, + "endOfLine": "auto", + "semi": true, + "tabWidth": 2, + "trailingComma": "none", + "arrowParens": "always", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "useTabs": false, + "quoteProps": "as-needed", + "insertPragma": false, + "requirePragma": false, + "jsxSingleQuote": true, + "printWidth": 120 + } + ], + "max-len": [ + "error", + { + "code": 120, + "ignoreUrls": true, + "ignorePattern": "(className=\\{[\\s\\S]*\\}|.*\\}|'.*'|className='.*')" // Ignore classnames + } + ] + }, + "globals": { + "React": true, + "expect": true, + "jsdom": true, + "JSX": true + }, + "overrides": [ + // Configuration for TypeScript files + { + "files": ["**/*.ts", "**/*.tsx", "netlify/*.ts"], + "plugins": [ + "@typescript-eslint", + "tailwindcss", + "unused-imports", + "simple-import-sort" + ], + "extends": [ + "plugin:tailwindcss/recommended", + "airbnb-typescript", + "next/core-web-vitals", + "plugin:prettier/recommended" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "rules": { + "react/destructuring-assignment": "off", // Vscode doesn't support automatically destructuring, it's a pain to add a new variable + "react/require-default-props": "off", // Allow non-defined react props as undefined + "react/jsx-props-no-spreading": "off", // _app.tsx uses spread operator and also, react-hook-form + "react-hooks/exhaustive-deps": "off", // Incorrectly report needed dependency with Next.js router + "@next/next/no-img-element": "off", // We currently not using next/image because it isn't supported with SSG mode + "@next/next/link-passhref": "off", // Only needed when the child of Link wraps an tag + "@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier + "@typescript-eslint/indent": "off", // Avoid conflict rule between Eslint and Prettier + "@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary + "no-restricted-syntax": [ + "error", + "ForInStatement", + "LabeledStatement", + "WithStatement" + ], // Overrides Airbnb configuration and enable no-restricted-syntax + "import/prefer-default-export": "off", // Named export is easier to refactor automatically + "tailwindcss/no-custom-classname": "off", // Disabled otherwise nightmare to allow each custom tailwind classes + "simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort` + "simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort` + "@typescript-eslint/no-unused-vars": "off", + "class-methods-use-this": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_" } + ], + // Variables + "init-declarations": "off", + "no-catch-shadow": "off", + "no-delete-var": "error", + "no-label-var": "error", + "no-restricted-globals": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-unused-vars": "error", + "no-use-before-define": "error", + // Styling + "array-bracket-newline": "off", + "array-bracket-spacing": "error", + "array-element-newline": "off", + "block-spacing": "error", + "brace-style": [ + "off", + "stroustrup", + { + "allowSingleLine": true + } + ], + "camelcase": "off", + "capitalized-comments": "off", + "comma-dangle": [ + "error", + "never" + ], + "comma-spacing": [ + 2, + { + "before": false, + "after": true + } + ], + "comma-style": [ + "error", + "last" + ], + "eol-last": "error", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": "off", + "func-style": "off", + "jsx-quotes": [ + "error", + "prefer-single" + ], + "key-spacing": "error", + "keyword-spacing": "error", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": [ + "error", + { + "beforeBlockComment": true, + "afterBlockComment": false, + "beforeLineComment": false, + "afterLineComment": false, + "allowBlockStart": true, + "allowBlockEnd": false, + "allowObjectStart": true, + "allowObjectEnd": false, + "allowArrayStart": true, + "allowArrayEnd": false + } + ], + "max-depth": "error", + "max-lines": [ + "error", + { + "max": 2000 + } + ], + "max-nested-callbacks": "error", + "max-statements-per-line": [ + "error", + { + "max": 2 + } + ], + "multiline-comment-style": "off", + "multiline-ternary": "off", + "new-cap": "off", + "new-parens": "error", + "newline-per-chained-call": [ + "error", + { + "ignoreChainWithDepth": 4 + } + ], + "newline-after-var": ["error", "always"], + "no-array-constructor": "error", + "no-lonely-if": "error", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": "error", + "no-multi-assign": "off", + "no-multiple-empty-lines": [ + "error", + { + "max": 1 + } + ], + "no-negated-condition": "error", + "no-nested-ternary": "error", + "no-new-object": "error", + "no-plusplus": "off", + "no-tabs": "error", + "no-ternary": "off", + "no-trailing-spaces": "error", + "no-unneeded-ternary": "error", + "no-whitespace-before-property": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "off", + "object-curly-spacing": [ + "error", + "always" + ], + "object-property-newline": "off", + "padded-blocks": [ + "error", + "never" + ], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "*", + "next": "return" + }, + { + "blankLine": "always", + "prev": [ + "const", + "let", + "var" + ], + "next": "*" + }, + { + "blankLine": "any", + "prev": [ + "const", + "let", + "var" + ], + "next": [ + "const", + "let", + "var" + ] + } + ], + "quote-props": [ + "error", + "as-needed" + ], + "quotes": [ + "error", + "single", + { + "avoidEscape": true + } + ], + "require-jsdoc": "warn", + "semi": "error", + "semi-spacing": "error", + "semi-style": [ + "error", + "last" + ], + "sort-keys": "off", + "sort-vars": "off", + "space-before-blocks": "error", + "space-before-function-paren": "error", + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": [ + "error", + "always", + { + "block": { + "exceptions": [ + "!" + ] + } + } + ], + "switch-colon-spacing": "error" + } + }, + { + "files": ["components/logos/*"], + "rules": { + "max-len": "off" + } + } + ] +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 233ad25c5542..000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - extends: ['@anshgoyalevil/eslint-config'], - rules: { - // add any additional or overridden rules here - } -} \ No newline at end of file diff --git a/.github/workflows/cypress-tests.yml b/.github/workflows/cypress-tests.yml deleted file mode 100644 index acc9ca227fb5..000000000000 --- a/.github/workflows/cypress-tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Run tests -on: - push: - branches: - - master - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - -jobs: - cypress-run: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - containers: [0, 1, 2, 3, 4, 5, 6, 7] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Check package-lock version - uses: asyncapi/.github/.github/actions/get-node-version-from-package-lock@master - id: lockversion - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: "${{ steps.lockversion.outputs.version }}" - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - - name: Install dependencies - run: npm install - - - name: Cypress Tests are running - run: node ./scripts/index.js && npx cypress run --component --spec $(node cypress-parallel.js ${{ matrix.containers }} 8) diff --git a/.gitignore b/.gitignore index 8a6940cc8107..1aa4c1d8c8aa 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ meetings.json .env cypress/screenshots cypress/videos -/config/finance/json-data/* \ No newline at end of file +/config/finance/json-data/* +*.mdx diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index fa29cdfff915..000000000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -** \ No newline at end of file diff --git a/.remarkrc b/.remarkrc new file mode 100644 index 000000000000..5c52f7c17d5d --- /dev/null +++ b/.remarkrc @@ -0,0 +1,11 @@ +{ + "plugins": [ + "lint", + "mdx" + ], + "settings": { + "mdx": { + "acornInjectPlugins": [] + } + } +} diff --git a/README.md b/README.md index 37f8ff838522..f394899ca9a3 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ This repository contains the sources of AsyncAPI website: Use the following tools to set up the project: -- [Node.js](https://nodejs.org/) v16.0.0+ -- [npm](https://www.npmjs.com/) v8.10.0+ +- [Node.js](https://nodejs.org/) v20.12.0+ +- [npm](https://www.npmjs.com/) v10.5.0+ ## Run locally @@ -117,6 +117,39 @@ After cloning repository to your local, perform the following steps from the roo Now you're running AsyncAPI website in a development mode. Container is mapped with your local copy of the website. Whenever you make changes to the code, the website will refresh and changes visible in localhost:3000. +## Lint the code +To lint the code, run the following command: +``` +npm run lint +``` + +To fix the linting issues, run the following command: +``` +npm run lint:fix +``` + +To lint the mdx files, run the following command: +``` +npm run lint:mdx +``` + +## Start the production server +To build and run a production-ready website, run the following command: +``` +npm run build && npm run start +``` +Generated files of the website go in the `.next` folder. + +## Start the netlify production server +Start a local development server for the build tool using the configuration and environment variables set for local development with the Netlify CLI: +``` +netlify dev +``` +To start the server using the configuration and environment variables set for `dev` or `all` deploy contexts, run the following command: +``` +netlify dev --context production +``` + ## Updating information about project finance AsyncAPI Financial Summary page aims to provide transparency and clarity regarding the organization's financial activities. It serves as a platform to showcase how donations are accepted, different sponsorship options, and how the generated funds are utilized. @@ -196,24 +229,34 @@ This repository has the following structure: ```text - ├── .github # Definitions of GitHub workflows, pull request and issue templates - ├── components # Various generic components such as "Button", "Figure", etc. - ├── config # Transformed static data to display on the pages such as blog posts etc. - ├── context # Various React's contexts used in website - ├── css # Various CSS files - ├── lib # Various JS code for preparing static data to render in pages - ├── pages # Website's pages source. It includes raw markdown files and React page templates. - │ ├── about # Raw blog for /about page - │ ├── blog # Blog posts - │ ├── docs # Blog for /docs/* pages - │ └── tools # Various pages to describe tools - ├── public # Data for site metadata and static blog such as images - ├── scripts # Scripts used in the build and dev processes - ├── next.config.js # Next.js configuration file - ├── netlify # Code that runs on Netlify - │ ├── edge-functions # Netlify Edge-Functions code - ├── postcss.config.js # PostCSS configuration file - └── tailwind.config.js # TailwindCSS configuration file + ├── .github # Definitions of GitHub workflows, pull request and issue templates + ├── assets # Various assets + | ├── docs # Documentation assets + | | fragments # Docuentations for CLI installation and contribution. + ├── components # Various generic components such as "Button", "Figure", etc. + ├── config # Transformed static data to display on the pages such as blog posts etc. + ├── context # Various React's contexts used in website + ├── locales # Translations for the website + ├── markdown # Markdown files for the website + ├── about # Markdown files for the /about page + ├── blog # Markdown files for the blog posts + ├── docs # Markdown files for the /docs/* pages + ├── netlify # Contains Netlify serverless functions to run on Netlify + ├── pages # Website's pages source. It includes raw markdown files and React page templates. + │ ├── about # Raw blog for /about page + │ ├── blog # Blog posts + │ ├── docs # Blog for /docs/* pages + │ └── tools # Various pages to describe tools + ├── public # Data for site metadata and static blog such as images + ├── scripts # Scripts used in the build and dev processes + ├── styles # Various CSS files + ├── templates # Various template markdown files + ├── types # Various typeScript types used in the website + ├── utils # Various JS code for preparing static data to render in pages + ├── next.config.mjs # Next.js configuration file + ├── README.md # Project's README file + ├── tailwind.config.js # TailwindCSS configuration file + └── tsconfig.json # TypeScript configuration file ``` ## Connect with AsyncAPI Community diff --git a/components/AlgoliaSearch.js b/components/AlgoliaSearch.js deleted file mode 100644 index 96a98dcc2735..000000000000 --- a/components/AlgoliaSearch.js +++ /dev/null @@ -1,231 +0,0 @@ -import { useState, useEffect, useCallback, useRef, createContext, useContext } from 'react' -import { createPortal } from 'react-dom' -import { useRouter } from 'next/router' -import Link from 'next/link' -import Head from 'next/head' -import { DocSearchModal } from '@docsearch/react' -import clsx from 'clsx' - -export const INDEX_NAME = 'asyncapi'; -export const DOCS_INDEX_NAME = 'asyncapi-docs'; -const APP_ID = 'Z621OGRI9Y'; -const API_KEY = '5a4122ae46ce865146d23d3530595d38'; - -const SearchContext = createContext({}); - -export default function AlgoliaSearch({ children }) { - const [isOpen, setIsOpen] = useState(false); - const [indexName, setIndexName] = useState(INDEX_NAME); - const [initialQuery, setInitialQuery] = useState(null); - - const onOpen = useCallback((_indexName) => { - _indexName && setIndexName(_indexName); - setIsOpen(true) - }, [setIsOpen, setIndexName]); - - const onClose = useCallback(() => { - setIsOpen(false); - }, [setIsOpen]); - - const onInput = useCallback( - (e) => { - setIsOpen(true) - setInitialQuery(e.key) - }, - [setIsOpen, setInitialQuery] - ); - - useDocSearchKeyboardEvents({ - isOpen, - onOpen, - onClose, - onInput, - }); - - return ( - <> - - - - - {children} - - {isOpen && } - - ); -} - -function AlgoliaModal({ onClose, initialQuery, indexName }) { - const router = useRouter(); - - return createPortal( - { - return `https://github.com/asyncapi/website/issues/new?title=Cannot%20search%20given%20query:%20${query}`; - }} - />, - document.body, - ); -} - -function Hit({ hit, children }) { - return ( - - - {children} - - - ) -} - -function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) { - useEffect(() => { - function onKeyDown(event) { - if ( - (event.keyCode === 27 && isOpen) || - (event.key === 'k' && (event.metaKey || event.ctrlKey)) || - (!isEditingContent(event) && event.key === '/' && !isOpen) - ) { - event.preventDefault(); - - if (isOpen) { - onClose() - } else if (!document.body.classList.contains('DocSearch--active')) { - let indexName = INDEX_NAME; - if (typeof document !== 'undefined') { - const loc = document.location; - indexName = loc.pathname.startsWith('/docs') ? DOCS_INDEX_NAME : INDEX_NAME; - } - onOpen(indexName) - } - } - } - - window.addEventListener('keydown', onKeyDown) - return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [isOpen, onOpen, onClose]); -} - -export function SearchButton({ children, indexName = INDEX_NAME, ...props }) { - const { onOpen, onInput } = useContext(SearchContext); - const searchButtonRef = useRef(); - const actionKey = getActionKey(); - - useEffect(() => { - function onKeyDown(event) { - if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) { - if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { - onInput(event) - } - } - } - - window.addEventListener('keydown', onKeyDown) - return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [onInput, searchButtonRef]); - - return ( - - ); -} - -function isEditingContent(event) { - const element = event.target; - const tagName = element.tagName; - - return ( - element.isContentEditable || - tagName === 'INPUT' || - tagName === 'SELECT' || - tagName === 'TEXTAREA' - ) -} - -function getActionKey() { - if (typeof navigator !== 'undefined') { - if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgentData?.platform || navigator.platform)) { - return { - shortKey: '⌘', - key: 'Command', - } - } - return { - shortKey: 'Ctrl', - key: 'Control', - } - } -} - -function transformItems(items) { - return items.map((item, index) => { - // We transform the absolute URL into a relative URL to - // leverage Next's preloading. - const a = document.createElement('a') - a.href = item.url - - const hash = a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash - - if (item.hierarchy?.lvl0) { - item.hierarchy.lvl0 = item.hierarchy.lvl0.replace(/&/g, '&') - } - - if (item._highlightResult?.hierarchy?.lvl0?.value) { - item._highlightResult.hierarchy.lvl0.value = - item._highlightResult.hierarchy.lvl0.value.replace(/&/g, '&') - } - - return { - ...item, - url: `${a.pathname}${hash}`, - __is_result: () => true, - __is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0, - __is_child: () => - item.type !== 'lvl1' && - items.length > 1 && - items[0].type === 'lvl1' && - index !== 0, - __is_first: () => index === 1, - __is_last: () => index === items.length - 1 && index !== 0, - } - }); -} \ No newline at end of file diff --git a/components/AlgoliaSearch.tsx b/components/AlgoliaSearch.tsx new file mode 100644 index 000000000000..43b3cc7ce6b9 --- /dev/null +++ b/components/AlgoliaSearch.tsx @@ -0,0 +1,324 @@ +/* eslint-disable no-underscore-dangle */ +import { DocSearchModal } from '@docsearch/react'; +import type { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit } from '@docsearch/react/dist/esm/types'; +import clsx from 'clsx'; +import Head from 'next/head'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +export const INDEX_NAME = 'asyncapi'; +export const DOCS_INDEX_NAME = 'asyncapi-docs'; +const APP_ID = 'Z621OGRI9Y'; +const API_KEY = '5a4122ae46ce865146d23d3530595d38'; + +interface ISearchContext { + isOpen: boolean; + onOpen: (indexName?: string) => void; + onClose: () => void; + onInput?: (e: React.KeyboardEvent) => void; +} + +const SearchContext = createContext({} as ISearchContext); + +interface IHitProps { + hit: (StoredDocSearchHit | InternalDocSearchHit) & { + __is_result?: () => boolean; + __is_parent?: () => boolean; + __is_child?: () => boolean; + __is_first?: () => boolean; + __is_last?: () => boolean; + }; + children: React.ReactNode; +} + +interface AlgoliaModalProps { + onClose: (event?: React.MouseEvent) => void; + initialQuery: string; + indexName: string; +} + +interface IUseDocSearchKeyboardEvents { + isOpen: boolean; + onOpen: (indexName?: string) => void; + onClose: () => void; + onInput?: (e: React.KeyboardEvent) => void; +} + +type ISearchButtonProps = Omit, 'children'> & { + children?: React.ReactNode | (({ actionKey }: { actionKey: { shortKey: string; key: string } }) => React.ReactNode); + indexName?: string; +}; + +/** + * + * @description The function used to transform the items + * @param {StoredDocSearchHit[]} items - The items to be transformed + */ +function transformItems(items: DocSearchHit[]) { + return items.map((item, index) => { + // We transform the absolute URL into a relative URL to + // leverage Next's preloading. + const a = document.createElement('a'); + + a.href = item.url; + + const hash = a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash; + + if (item.hierarchy?.lvl0) { + // eslint-disable-next-line no-param-reassign + item.hierarchy.lvl0 = item.hierarchy.lvl0.replace(/&/g, '&'); + } + + return { + ...item, + url: `${a.pathname}${hash}`, + __is_result: () => true, + __is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0, + __is_child: () => item.type !== 'lvl1' && items.length > 1 && items[0].type === 'lvl1' && index !== 0, + __is_first: () => index === 1, + __is_last: () => index === items.length - 1 && index !== 0 + }; + }); +} + +/** + * + * @description The hit component used for the Algolia search + * @param {IHitProps} props - The props of the hit + */ +function Hit({ hit, children }: IHitProps) { + return ( + + {children} + + ); +} + +/** + * + * @description The Algolia modal used for searching the website + * @param {IAlgoliaModalProps} props - The props of the Algolia modal + */ +function AlgoliaModal({ onClose, initialQuery, indexName }: AlgoliaModalProps) { + const router = useRouter(); + + return createPortal( + { + return `https://github.com/asyncapi/website/issues/new?title=Cannot%20search%20given%20query:%20${query}`; + }} + />, + document.body + ); +} + +/** + * @description The function used to check if the content is being edited + * @param {KeyboardEvent} event - The keyboard event + * @returns {boolean} - Whether the content is being edited + */ +function isEditingContent(event: KeyboardEvent) { + const element = event.target; + const { tagName } = element as HTMLElement; + + return ( + (element as HTMLElement).isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' + ); +} + +/** + * @description The function used to get the action key + * @returns {Object} - The action key + */ +function getActionKey() { + if (typeof navigator !== 'undefined') { + if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent || navigator.platform)) { + return { + shortKey: '⌘', + key: 'Command' + }; + } + + return { + shortKey: 'Ctrl', + key: 'Control' + }; + } + + return { + shortKey: 'Ctrl', + key: 'Control' + }; +} + +/** + * + * @description The hook used for the Algolia search keyboard events + * @param {IUseDocSearchKeyboardEvents} props - The props of the useDocSearchKeyboardEvents hook + */ +function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKeyboardEvents) { + useEffect(() => { + /** + * @description The function used to handle the keyboard event. + * @description It opens the search modal when the '/' key is pressed + * @description It closes the search modal when the 'Escape' key is pressed + * @description It opens the search modal when the 'k' key is pressed with the 'Command' or 'Control' key + * @param {KeyboardEvent} event - The keyboard event + * @returns {void} + */ + function onKeyDown(event: KeyboardEvent): void { + if ( + (event.key === 'Escape' && isOpen) || + (event.key === 'k' && (event.metaKey || event.ctrlKey)) || + (!isEditingContent(event) && event.key === '/' && !isOpen) + ) { + event.preventDefault(); + + if (isOpen) { + onClose(); + } else if (!document.body.classList.contains('DocSearch--active')) { + let indexName = INDEX_NAME; + + if (typeof document !== 'undefined') { + const loc = document.location; + + indexName = loc.pathname.startsWith('/docs') ? DOCS_INDEX_NAME : INDEX_NAME; + } + onOpen(indexName); + } + } + } + + window.addEventListener('keydown', onKeyDown); + + return () => { + window.removeEventListener('keydown', onKeyDown); + }; + }, [isOpen, onOpen, onClose]); +} + +/** + * + * @description The Algolia search component used for searching the website + * @param {React.ReactNode} children - The content of the page + */ +export default function AlgoliaSearch({ children }: { children: React.ReactNode }) { + const [isOpen, setIsOpen] = useState(false); + const [indexName, setIndexName] = useState(INDEX_NAME); + const [initialQuery, setInitialQuery] = useState(); + + const onOpen = useCallback( + (_indexName?: string) => { + if (_indexName) { + setIndexName(_indexName); + } + setIsOpen(true); + }, + [setIsOpen, setIndexName] + ); + + const onClose = useCallback(() => { + setIsOpen(false); + }, [setIsOpen]); + + const onInput = useCallback( + (e: React.KeyboardEvent) => { + setIsOpen(true); + setInitialQuery(e.key); + }, + [setIsOpen, setInitialQuery] + ); + + useDocSearchKeyboardEvents({ + isOpen, + onOpen, + onClose, + onInput + }); + + return ( + <> + + + + {children} + {isOpen && } + + ); +} + +/** + * + * @description The search button component used for opening the Algolia search + * @param {ISearchButtonProps} props - The props of the search button + */ +export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISearchButtonProps) { + const { onOpen, onInput } = useContext(SearchContext); + const searchButtonRef = useRef(null); + const actionKey = getActionKey(); + + useEffect(() => { + /** + * @description It triggers the onInput event when a key is pressed and the search button is focused + * @description It starts search with the key pressed + * @param {KeyboardEvent} event - The keyboard event + * @returns {void} + */ + function onKeyDown(event: KeyboardEvent) { + if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) { + if (/[a-zA-Z0-9]/.test(event.key)) { + onInput(event as unknown as React.KeyboardEvent); + } + } + } + + window.addEventListener('keydown', onKeyDown); + + return () => { + window.removeEventListener('keydown', onKeyDown); + }; + }, [onInput, searchButtonRef]); + + return ( + + ); +} diff --git a/components/AsyncAPILogo.js b/components/AsyncAPILogo.js deleted file mode 100644 index 2604c5d65d7b..000000000000 --- a/components/AsyncAPILogo.js +++ /dev/null @@ -1,59 +0,0 @@ -export default function AsyncAPILogo({ className }) { - return ( - - AsyncAPI Logo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/components/AsyncAPILogo.tsx b/components/AsyncAPILogo.tsx new file mode 100644 index 000000000000..6b6a66de3fd2 --- /dev/null +++ b/components/AsyncAPILogo.tsx @@ -0,0 +1,139 @@ +import { twMerge } from 'tailwind-merge'; + +interface IAsyncAPILogoProps { + className?: string; +} + +/** + * @description A component that displays the AsyncAPI logo + * @param {string} props.className - The class name for the component + */ +export default function AsyncAPILogo({ className }: IAsyncAPILogoProps) { + return ( + + AsyncAPI Logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/components/AsyncAPILogoLight.js b/components/AsyncAPILogoLight.js deleted file mode 100644 index 678fd6a59e5d..000000000000 --- a/components/AsyncAPILogoLight.js +++ /dev/null @@ -1,59 +0,0 @@ -export default function AsyncAPILogoLight({ className='' }) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) - } \ No newline at end of file diff --git a/components/AsyncAPILogoLight.tsx b/components/AsyncAPILogoLight.tsx new file mode 100644 index 000000000000..73bb3f914401 --- /dev/null +++ b/components/AsyncAPILogoLight.tsx @@ -0,0 +1,149 @@ +import React from 'react'; + +interface AsyncAPILogoLightProps { + className?: string; +} + +/** + * @description The AsyncAPILogoLight component is the logo for AsyncAPI in light mode. + * @param {string} props.className The class name for the component + */ +export default function AsyncAPILogoLight({ className }: AsyncAPILogoLightProps) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/components/Asyncapi3Comparison.js b/components/Asyncapi3Comparison.js deleted file mode 100644 index cc9c273b3960..000000000000 --- a/components/Asyncapi3Comparison.js +++ /dev/null @@ -1,501 +0,0 @@ -import React, { useState } from 'react'; - -/** - * Used to compare how channels, operations and messages have changed - */ -export function Asyncapi3ChannelComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - Paths: false, - PathItem: false, - Operation: false, - Message: false, - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> - Channels - -
-
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> - Channel Item - -
-
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> - Operation (Publish and Subscribe) - -
-
-
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> - Messages -
- Message - -
- Headers -
-
- Payload -
-
-
-
-
-
-
-
-
-
-
-
-
-

AsyncAPI 3.0

- -
-
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> - Channels - -
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> - Channel -
-
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> - Messages -
- Message - -
- Headers -
-
- Payload -
-
-
-
-
-
-
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> - Operations -
-
- Operation -
-
- action (send or receive) -
-
- channel -
-
- messages -
-
-
-
-
-
-
-
- ) -} - -/** - * Shows the comparison between v2 and v3 for the channel IDs and channel address - */ -export function Asyncapi3IdAndAddressComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - Paths: false, - PathItem: false, - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> - Channels -
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> - Channel Item -
-
-
-
-
-

AsyncAPI 3.0

- -
-
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> - Channels - -
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> - Channel - -
-
- address -
-
-
-
-
-
-
- ) -} - -/** - * Compares how the server object changes from v2 to v3. - */ -export function Asyncapi3ServerComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - Host: false, - path: false, - Servers: false, - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
- Servers -
-
- Server -
-
setHoverState(prevState => ({ ...prevState, Host: true, Path: true }))} onMouseLeave={() => setHoverState({ Host: false, Path: false })}> -

Url

-
-
-
-
-
-
-
-
-

AsyncAPI 3.0

- -
-
- Servers -
-
- Server -
-
setHoverState(prevState => ({ ...prevState, Host: true }))} onMouseLeave={() => setHoverState({ Host: false })}> -

Host

-
-
setHoverState(prevState => ({ ...prevState, Path: true }))} onMouseLeave={() => setHoverState({ Path: false })}> -

Pathname

-
-
-
-
-
-
-
-
- ) -} - -/** - * Compare how the meta data moved place between v2 and v3 - */ -export function Asyncapi3MetaComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - Info: false, - Tags: false, - External: false - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> - Info -
-
-
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> -

Tags

-
-
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> -

External Docs

-
-
-
-
-
-

AsyncAPI 3.0

- -
-
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> - Info -
-
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> -

Tags

-
-
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> -

External Docs

-
-
-
-
-
-
- ) -} - -/** - * Compares how operations changed from v2 to v3 - */ -export function Asyncapi3OperationComparison({ className = '' }) { - return ( -
-
-

AsyncAPI 2.x

- -
-
- Channels - -
-
- Channel Item - -
-
- Operation (Publish and Subscribe) -
-
-
-
-
-
-
-
-

AsyncAPI 3.0

- -
-
- Operations -
-
- Operation - -
-
- action (send or receive) -
-
-
-
-
-
-
-
- ) -} - -/** - * Compares how the schema and schemaFormat changed location from v2 to v3 - */ -export function Asyncapi3SchemaFormatComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - SchemaFormat: false, - Payload: false, - Schema: false - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
- components | channels - -
-
- messages - -
-
- message -
-
setHoverState(prevState => ({ ...prevState, SchemaFormat: true }))} onMouseLeave={() => setHoverState({ SchemaFormat: false })}> - schemaFormat -
- -
setHoverState(prevState => ({ ...prevState, Payload: true }))} onMouseLeave={() => setHoverState({ Payload: false })}> - payload -
-
setHoverState(prevState => ({ ...prevState, Schema: true }))} onMouseLeave={() => setHoverState({ Schema: false })}> - schema -
-
-
-
-
-
-
-
-
-
-
-
-

AsyncAPI 3.0

- -
-
- components | channels - -
-
- messages - -
-
- message -
-
setHoverState(prevState => ({ ...prevState, Payload: true }))} onMouseLeave={() => setHoverState({ Payload: false })}> - payload - -
-
setHoverState(prevState => ({ ...prevState, SchemaFormat: true }))} onMouseLeave={() => setHoverState({ SchemaFormat: false })}> - schemaFormat -
-
setHoverState(prevState => ({ ...prevState, Schema: true }))} onMouseLeave={() => setHoverState({ Schema: false })}> - schema -
-
-
-
-
-
-
-
-
-
-
-
- ) -} - -/** - * Compares how the parameter object changed location from v2 to v3 - */ -export function Asyncapi3ParameterComparison({ className = '' }) { - const [hoverState, setHoverState] = useState({ - location: false, - description: false, - enum: false, - examples: false, - default: false - }); - - return ( -
-
-

AsyncAPI 2.x

- -
-
- components | channels - -
-
- parameters - -
-
- parameter -
-
setHoverState(prevState => ({ ...prevState, location: true }))} onMouseLeave={() => setHoverState({ location: false })}> - location -
-
setHoverState(prevState => ({ ...prevState, description: true }))} onMouseLeave={() => setHoverState({ description: false })}> - description -
- -
- schema -
-
type
-
setHoverState(prevState => ({ ...prevState, enum: true }))} onMouseLeave={() => setHoverState({ enum: false })}> - enum -
-
setHoverState(prevState => ({ ...prevState, examples: true }))} onMouseLeave={() => setHoverState({ examples: false })}> - examples -
-
setHoverState(prevState => ({ ...prevState, default: true }))} onMouseLeave={() => setHoverState({ default: false })}> - default -
-
setHoverState(prevState => ({ ...prevState, description: true }))} onMouseLeave={() => setHoverState({ description: false })}> - description -
-
pattern
-
multipleOf
-
And all other properties
-
-
-
-
-
-
-
-
-
-
-
-

AsyncAPI 3.0

- -
-
- components | channels - -
-
- parameters - -
-
- parameter -
-
setHoverState(prevState => ({ ...prevState, location: true }))} onMouseLeave={() => setHoverState({ location: false })}> - location -
-
setHoverState(prevState => ({ ...prevState, description: true }))} onMouseLeave={() => setHoverState({ description: false })}> - description -
-
setHoverState(prevState => ({ ...prevState, enum: true }))} onMouseLeave={() => setHoverState({ enum: false })}> - enum -
-
setHoverState(prevState => ({ ...prevState, examples: true }))} onMouseLeave={() => setHoverState({ examples: false })}> - examples -
-
setHoverState(prevState => ({ ...prevState, default: true }))} onMouseLeave={() => setHoverState({ default: false })}> - default -
-
-
-
-
-
-
-
-
-
- ) -} diff --git a/components/Asyncapi3Comparison/Asyncapi3ChannelComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3ChannelComparison.tsx new file mode 100644 index 000000000000..4b86855e035c --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3ChannelComparison.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; + +export interface HoverState { + Paths: boolean; + PathItem: boolean; + Operation: boolean; + Message: boolean; +} + +export interface AsyncAPI3ChannelComparisonProps { + className?: string; +} + +/** + * @description Component to compare AsyncAPI 2.x and AsyncAPI 3.0 channels. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3ChannelComparison({ className = '' }: AsyncAPI3ChannelComparisonProps) { + const [hoverState, setHoverState] = useState({ + Paths: false, + PathItem: false, + Operation: false, + Message: false + }); + + const handleMouseEnter = (key: keyof HoverState) => { + setHoverState((prevState) => ({ ...prevState, [key]: true })); + }; + + const handleMouseLeave = (key: keyof HoverState) => { + setHoverState((prevState) => ({ ...prevState, [key]: false })); + }; + + return ( +
+
+

AsyncAPI 2.x

+
+
handleMouseEnter('Paths')} + onMouseLeave={() => handleMouseLeave('Paths')} + > + Channels +
+
handleMouseEnter('PathItem')} + onMouseLeave={() => handleMouseLeave('PathItem')} + > + Channel Item +
+
handleMouseEnter('Operation')} + onMouseLeave={() => handleMouseLeave('Operation')} + > + Operation (Publish and Subscribe) +
+
+
handleMouseEnter('Message')} + onMouseLeave={() => handleMouseLeave('Message')} + > + Messages +
+ Message +
Headers
+
Payload
+
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+
+
handleMouseEnter('Paths')} + onMouseLeave={() => handleMouseLeave('Paths')} + > + Channels +
handleMouseEnter('PathItem')} + onMouseLeave={() => handleMouseLeave('PathItem')} + > + Channel +
+
handleMouseEnter('Message')} + onMouseLeave={() => handleMouseLeave('Message')} + > + Messages +
+ Message +
Headers
+
Payload
+
+
+
+
+
+
handleMouseEnter('Operation')} + onMouseLeave={() => handleMouseLeave('Operation')} + > + Operations +
+
+ Operation +
+
+ action (send or receive) +
+
channel
+
messages
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3IdAndAddressComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3IdAndAddressComparison.tsx new file mode 100644 index 000000000000..14ec15ee2bf5 --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3IdAndAddressComparison.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; + +export interface HoverState { + Paths: boolean; + PathItem: boolean; +} + +export interface AsyncAPI3IdAndAddressComparisonProps { + className?: string; +} + +/** + * @description Component for comparing AsyncAPI versions based on ID and address. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3IdAndAddressComparison({ className = '' }: AsyncAPI3IdAndAddressComparisonProps) { + const [hoverState, setHoverState] = useState({ + Paths: false, + PathItem: false + }); + + const handleMouseEnter = (key: keyof HoverState) => { + setHoverState((prevState) => ({ ...prevState, [key]: true })); + }; + + const handleMouseLeave = (key: keyof HoverState) => { + setHoverState((prevState) => ({ ...prevState, [key]: false })); + }; + + return ( +
+
+

AsyncAPI 2.x

+
+
handleMouseEnter('Paths')} + onMouseLeave={() => handleMouseLeave('Paths')} + > + Channels +
handleMouseEnter('PathItem')} + onMouseLeave={() => handleMouseLeave('PathItem')} + > + Channel Item +
+
+
+
+
+

AsyncAPI 3.0

+
+
handleMouseEnter('Paths')} + onMouseLeave={() => handleMouseLeave('Paths')} + > + Channels +
handleMouseEnter('PathItem')} + onMouseLeave={() => handleMouseLeave('PathItem')} + > + Channel +
+
address
+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3MetaComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3MetaComparison.tsx new file mode 100644 index 000000000000..a979c17f4595 --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3MetaComparison.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; + +export interface Asyncapi3MetaComparisonProps { + className?: string; +} + +export interface HoverState { + Info: boolean; + Tags: boolean; + External: boolean; +} + +/** + * @description React component for comparing AsyncAPI metadata between versions 2.x and 3.0. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3MetaComparison({ className = '' }: Asyncapi3MetaComparisonProps) { + const [hoverState, setHoverState] = useState({ + Info: false, + Tags: false, + External: false + }); + + return ( +
+
+

AsyncAPI 2.x

+
+
setHoverState((prevState) => ({ ...prevState, Info: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Info: false }))} + > + Info +
+
+
setHoverState((prevState) => ({ ...prevState, Tags: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Tags: false }))} + > +

Tags

+
+
setHoverState((prevState) => ({ ...prevState, External: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, External: false }))} + > +

External Docs

+
+
+
+
+
+

AsyncAPI 3.0

+
+
setHoverState((prevState) => ({ ...prevState, Info: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Info: false }))} + > + Info +
+
setHoverState((prevState) => ({ ...prevState, Tags: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Tags: false }))} + > +

Tags

+
+
setHoverState((prevState) => ({ ...prevState, External: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, External: false }))} + > +

External Docs

+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3OperationComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3OperationComparison.tsx new file mode 100644 index 000000000000..a2dee6abca21 --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3OperationComparison.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +export interface Asyncapi3OperationComparisonProps { + className?: string; +} + +/** + * @description React component for comparing AsyncAPI operations between versions 2.x and 3.0. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3OperationComparison({ className = '' }: Asyncapi3OperationComparisonProps) { + return ( +
+
+

AsyncAPI 2.x

+
+
+ Channels +
+
+ Channel Item +
+
Operation (Publish and Subscribe)
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+
+
+ Operations +
+
+ Operation +
+
action (send or receive)
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3ParameterComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3ParameterComparison.tsx new file mode 100644 index 000000000000..e5ad893aa02f --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3ParameterComparison.tsx @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; + +export interface Asyncapi3ParameterComparisonProps { + className?: string; +} + +/** + * @description React component for comparing AsyncAPI parameters between versions 2.x and 3.0. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3ParameterComparison({ className = '' }: Asyncapi3ParameterComparisonProps) { + const [hoverState, setHoverState] = useState({ + location: false, + description: false, + enum: false, + examples: false, + default: false + }); + + return ( +
+
+

AsyncAPI 2.x

+
+
+ components | channels +
+
+ parameters +
+
+ parameter +
+
setHoverState((prevState) => ({ ...prevState, location: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, location: false }))} + > + location +
+
setHoverState((prevState) => ({ ...prevState, description: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, description: false }))} + > + description +
+
+ schema +
+
type
+
setHoverState((prevState) => ({ ...prevState, enum: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, enum: false }))} + > + enum +
+
setHoverState((prevState) => ({ ...prevState, examples: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, examples: false }))} + > + examples +
+
setHoverState((prevState) => ({ ...prevState, default: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, default: false }))} + > + default +
+
setHoverState((prevState) => ({ ...prevState, description: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, description: false }))} + > + description +
+
pattern
+
multipleOf
+
And all other properties
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+
+
+ components | channels +
+
+ parameters +
+
+ parameter +
+
setHoverState((prevState) => ({ ...prevState, location: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, location: false }))} + > + location +
+
setHoverState((prevState) => ({ ...prevState, description: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, description: false }))} + > + description +
+
setHoverState((prevState) => ({ ...prevState, enum: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, enum: false }))} + > + enum +
+
setHoverState((prevState) => ({ ...prevState, examples: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, examples: false }))} + > + examples +
+
setHoverState((prevState) => ({ ...prevState, default: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, default: false }))} + > + default +
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3SchemaFormatComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3SchemaFormatComparison.tsx new file mode 100644 index 000000000000..baa19415bcab --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3SchemaFormatComparison.tsx @@ -0,0 +1,108 @@ +import React, { useState } from 'react'; + +export interface Asyncapi3SchemaFormatComparisonProps { + className?: string; +} + +/** + * @description React component for comparing AsyncAPI schema formats between versions 2.x and 3.0. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3SchemaFormatComparison({ className = '' }: Asyncapi3SchemaFormatComparisonProps) { + const [hoverState, setHoverState] = useState({ + SchemaFormat: false, + Payload: false, + Schema: false + }); + + return ( +
+
+

AsyncAPI 2.x

+
+
+ components | channels +
+
+ messages +
+
+ message +
+
setHoverState((prevState) => ({ ...prevState, SchemaFormat: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, SchemaFormat: false }))} + > + schemaFormat +
+
setHoverState((prevState) => ({ ...prevState, Payload: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Payload: false }))} + > + payload +
+
setHoverState((prevState) => ({ ...prevState, Schema: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Schema: false }))} + > + schema +
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+
+
+ components | channels +
+
+ messages +
+
+ message +
+
setHoverState((prevState) => ({ ...prevState, Payload: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Payload: false }))} + > + payload +
+
setHoverState((prevState) => ({ ...prevState, SchemaFormat: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, SchemaFormat: false }))} + > + schemaFormat +
+
setHoverState((prevState) => ({ ...prevState, Schema: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Schema: false }))} + > + schema +
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Asyncapi3Comparison/Asyncapi3ServerComparison.tsx b/components/Asyncapi3Comparison/Asyncapi3ServerComparison.tsx new file mode 100644 index 000000000000..bbc5b74e3146 --- /dev/null +++ b/components/Asyncapi3Comparison/Asyncapi3ServerComparison.tsx @@ -0,0 +1,77 @@ +import React, { useState } from 'react'; + +export interface Asyncapi3ServerComparisonProps { + className?: string; +} + +export interface HoverState { + Host: boolean; + Path: boolean; +} + +/** + * @description React component for comparing AsyncAPI servers between versions 2.x and 3.0. + * @param {string} [props.className=''] - Additional CSS classes for styling. + */ +export default function Asyncapi3ServerComparison({ className = '' }: Asyncapi3ServerComparisonProps) { + const [hoverState, setHoverState] = useState({ + Host: false, + Path: false + }); + + return ( +
+
+

AsyncAPI 2.x

+
+
+ Servers +
+
+ Server +
+
setHoverState((prevState) => ({ ...prevState, Host: true, Path: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Host: false, Path: false }))} + > +

Url

+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+
+
+ Servers +
+
+ Server +
+
setHoverState((prevState) => ({ ...prevState, Host: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Host: false }))} + > +

Host

+
+
setHoverState((prevState) => ({ ...prevState, Path: true }))} + onMouseLeave={() => setHoverState((prevState) => ({ ...prevState, Path: false }))} + > +

Pathname

+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/AuthorAvatars.js b/components/AuthorAvatars.js deleted file mode 100644 index 0ce49ba1fc42..000000000000 --- a/components/AuthorAvatars.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -export default function AuthorAvatars({ authors = [] }) { - return ( - authors.map((author, index) => { - let avatar = ( - 0 ? `absolute left-${index * 7} top-0` : `relative mr-${(authors.length - 1) * 7}`} z-${(authors.length - 1 - index) * 10} h-10 w-10 border-2 border-white rounded-full object-cover hover:z-50`} - src={author.photo} - loading="lazy" - data-testid="AuthorAvatars-img" - /> - ); - - return author.link ? ( - - {avatar} - - ) : ( - {avatar} - ); - }) - ); -} diff --git a/components/AuthorAvatars.tsx b/components/AuthorAvatars.tsx new file mode 100644 index 000000000000..03bf39dcb447 --- /dev/null +++ b/components/AuthorAvatars.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +interface Author { + name: string; + photo: string; + link?: string; +} + +interface AuthorAvatarsProps { + authors: Author[]; +} + +/** + * @description This component takes an array of authors and renders their avatars. + * @param {AuthorAvatarsProps} props - The component props. + * @param {Author[]} props.authors - The authors to render avatars for. + */ +export default function AuthorAvatars({ authors = [] }: AuthorAvatarsProps) { + return ( + <> + {authors.map((author, index) => { + const avatar = ( + 0 ? `left- absolute${index * 7} top-0` : `mr- relative${(authors.length - 1) * 7}`} + z-${(authors.length - 1 - index) * 10} size-10 rounded-full border-2 + border-white object-cover hover:z-50`} + src={author.photo} + loading='lazy' + data-testid='AuthorAvatars-img' + alt={author.name} // Added alt attribute here + /> + ); + + return author.link ? ( + + {avatar} + + ) : ( + {avatar} + ); + })} + + ); +} diff --git a/components/Calendar.js b/components/Calendar.js deleted file mode 100644 index f6a4947339a7..000000000000 --- a/components/Calendar.js +++ /dev/null @@ -1,60 +0,0 @@ -import eventsData from '../config/meetings.json'; -import GoogleCalendarButton from './buttons/GoogleCalendarButton'; -import Heading from './typography/Heading'; -import { getEvents } from '../lib/staticHelpers'; -import { useTranslation } from '../lib/i18n'; - -export default function Calendar({ className = '', size, text="text-left" }) { - - const { t } = useTranslation('common'); - - const CALENDAR_URL = - 'https://calendar.google.com/calendar/embed?src=c_q9tseiglomdsj6njuhvbpts11c%40group.calendar.google.com&ctz=UTC'; - const eventsExist = eventsData.length > 0; - return ( -
- - {t("calendar.title")} - - - {eventsExist ? -
- -
- : -
- {t("calendar.noMeetingsMessage")} -
- } -
- ); -} diff --git a/components/Calendar.tsx b/components/Calendar.tsx new file mode 100644 index 000000000000..2805abf2f7cd --- /dev/null +++ b/components/Calendar.tsx @@ -0,0 +1,64 @@ +import moment from 'moment'; +import { twMerge } from 'tailwind-merge'; + +import type { IEvent } from '@/types/event'; +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; + +import eventsData from '../config/meetings.json'; +import { useTranslation } from '../utils/i18n'; +import { getEvents } from '../utils/staticHelpers'; +import GoogleCalendarButton from './buttons/GoogleCalendarButton'; +import Heading from './typography/Heading'; + +interface ICalendarProps { + className?: string; + size: number; + text?: string; +} + +/** + * @description A component that displays a list of upcoming events + * @param {string} props.className - The class name for the component + * @param {number} props.size - The number of events to display + * @param {string} props.text - The text alignment for the component + */ +export default function Calendar({ className = '', size }: ICalendarProps) { + const { t } = useTranslation('common'); + + const CALENDAR_URL = + 'https://calendar.google.com/calendar/embed?src=c_q9tseiglomdsj6njuhvbpts11c%40group.calendar.google.com&ctz=UTC'; + const eventsExist = eventsData.length > 0; + + return ( +
+ + {t('calendar.title')} + + + {eventsExist ? ( +
+ +
+ ) : ( +
{t('calendar.noMeetingsMessage')}
+ )} +
+ ); +} diff --git a/components/Caption.js b/components/Caption.js deleted file mode 100644 index 7bc40bbc8381..000000000000 --- a/components/Caption.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function Caption ({children}) { - return ( -

- {children} -

- ) -} diff --git a/components/Caption.tsx b/components/Caption.tsx new file mode 100644 index 000000000000..cae2cafd770d --- /dev/null +++ b/components/Caption.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +interface CaptionProps { + children: string; +} + +/** + * @description This component displays textual captions. + * + * @param {CaptionProps} props - The props for the Caption component. + * @param {string} props.children - The content to be displayed as the caption. + */ +export default function Caption({ children }: CaptionProps) { + return ( +

+ {children} +

+ ); +} diff --git a/components/CaseStudyCard.js b/components/CaseStudyCard.js deleted file mode 100644 index 861a927ff55f..000000000000 --- a/components/CaseStudyCard.js +++ /dev/null @@ -1,29 +0,0 @@ -import Paragraph from './typography/Paragraph'; - -export default function CaseStudyCard({ - studies = [] -}) { - if(studies.length === 0){ - return null; - } - return ( -
- {studies.map((study, index) => ( - -
- - {study.company.name} - - - { study.company.description } - -
-
- ))} -
- ); -} diff --git a/components/CaseStudyCard.tsx b/components/CaseStudyCard.tsx new file mode 100644 index 000000000000..241e9b5aa84b --- /dev/null +++ b/components/CaseStudyCard.tsx @@ -0,0 +1,38 @@ +import type { ICaseStudies } from '@/types/post'; +import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; + +import Paragraph from './typography/Paragraph'; + +interface ICaseStudyCardProps { + studies?: ICaseStudies; +} + +/** + * @description A component that displays a list of case studies in a card format + * @param {ICaseStudies} props.studies - The list of case studies to display + */ +export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) { + if (studies.length === 0) { + return null; + } + + return ( +
+ {studies.map((study, index) => ( + +
+ + {study.company.name} + + + {study.company.description} + +
+
+ ))} +
+ ); +} diff --git a/components/CaseTOC.js b/components/CaseTOC.js deleted file mode 100644 index 0e7e5ac8f8d6..000000000000 --- a/components/CaseTOC.js +++ /dev/null @@ -1,157 +0,0 @@ -import { useMemo, useState } from "react"; -import Scrollspy from "react-scrollspy"; -import { twMerge } from "tailwind-merge"; -import ArrowRight from "./icons/ArrowRight"; -import { useHeadingsObserver } from "./helpers/useHeadingsObserver"; - -const checkIfActive = (item, currSelected) => { - return item.slug === currSelected || item.children?.some((child) => checkIfActive(child, currSelected)); -} - -const convertContentToTocItems = (content, level = 1) => { - const tocItems = []; - - for (let 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.children = children; - } - - tocItems.push(item); - } - - return tocItems; -}; - -function TOCItem({ item, index, currSelected, closeMenu }) { - const [open, setOpen] = useState(false); - const handleClick = () => { - closeMenu(); - setOpen(false); - }; - const active = useMemo(() => checkIfActive(item, currSelected), [item, currSelected]); - - return ( - <> - - {item.children && item.children.length > 0 && ( -
    - {item.children.map((item, index) => ( - - ))} -
- )} - - ); -} - -export default function CaseTOC({ - className, - cssBreakingPoint = "xl", - toc, - contentSelector, -}) { - if (!toc || !toc.length) return null; - const tocItems = useMemo(() => convertContentToTocItems(toc), [toc]); - const [open, setOpen] = useState(false); - const { currActive: selected } = useHeadingsObserver(); - - return ( -
-
-
- On this page -
-
setOpen(!open)} - > - -
-
-
-
    - {tocItems.map((item, index) => ( - setOpen(false)} - currSelected={selected} - /> - ))} -
-
-
- ); -} diff --git a/components/CaseTOC.tsx b/components/CaseTOC.tsx new file mode 100644 index 000000000000..6008a6ea1562 --- /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 ( +
+
+
+ On this page +
+
setOpen(!open)} + > + +
+
+
+
    + {tocItems.map((item, index) => ( + setOpen(false)} + currSelected={selected || ''} + /> + ))} +
+
+
+ ); +} diff --git a/components/ClickableLogo.js b/components/ClickableLogo.js deleted file mode 100644 index b30653d5307e..000000000000 --- a/components/ClickableLogo.js +++ /dev/null @@ -1,16 +0,0 @@ -import Link from 'next/link' -import AsyncAPILogo from './AsyncAPILogo' - -export default function ClickableLogo({ - href = '/', - className = 'flex', - logoClassName, -}) { - return ( - - - - - - ) -} \ No newline at end of file diff --git a/components/ClickableLogo.tsx b/components/ClickableLogo.tsx new file mode 100644 index 000000000000..0ce7f081e4f3 --- /dev/null +++ b/components/ClickableLogo.tsx @@ -0,0 +1,23 @@ +import Link from 'next/link'; + +import AsyncAPILogo from './AsyncAPILogo'; + +interface IClickableLogoProps { + href?: string; + className?: string; + logoClassName?: string; +} + +/** + * @description A component that displays the AsyncAPI logo as a clickable link + * @param {string} props.href - The URL to link to + * @param {string} props.className - The class name for the component + * @param {string} props.logoClassName - The class name for the logo + */ +export default function ClickableLogo({ href = '/', className = 'flex', logoClassName }: IClickableLogoProps) { + return ( + + + + ); +} diff --git a/components/DemoAnimation.js b/components/DemoAnimation.js deleted file mode 100644 index ebccb110bb18..000000000000 --- a/components/DemoAnimation.js +++ /dev/null @@ -1,319 +0,0 @@ -import { useState, useEffect } from 'react' -import Typing from 'react-typing-animation' -import MacWindow from './MacWindow' -import ArrowRight from './icons/ArrowRight' -import OpenInStudioButton from './buttons/OpenInStudioButton' -import Heading from './typography/Heading' - -export default function DemoAnimation({ className = '' }) { - const [started, setStarted] = useState(true) - const [showInfo, setShowInfo] = useState(false) - const [showChannelsAndOperation, setShowChannelsAndOperation] = useState(false) - const [showUntilMessagePayload, setShowUntilMessagePayload] = useState(false) - const [showDisplayName, setShowDisplayName] = useState(false) - const [showEmail, setShowEmail] = useState(false) - const [showDisplayNameDescription, setShowDisplayNameDescription] = useState(false) - const [showEmailDescription, setShowEmailDescription] = useState(false) - const [finished, setFinished] = useState(false) - const [showControls, setShowControls] = useState(false) - const typingSpeed = 60 - - useEffect(() => { - if (finished) { - setTimeout(() => { - setShowControls(true) - }, 2000) - } - }, [finished]) - - function transitionClassNames(condition) { - return `transition-all duration-500 ease-in-out overflow-hidden ${condition ? 'opacity-100 max-h-auto' : 'max-h-0 opacity-0'}` - } - - function renderTyping(children, callback) { - return ( - } onFinishedTyping={callback}> - {children} - - ) - } - - function renderInfoBlock(callback) { - const descriptionCallback = () => setFinished(true) - - const common = ( - <> -
- asyncapi: 3.0.0 -
-
- info: -
-
-   title: Account Service -
-
-   version: 1.0.0 -
- - - ) - - if (showEmailDescription) { - return ( - <> - {common} - } onFinishedTyping={descriptionCallback}> -
-   description: This service is in charge of processing user signups :rocket: -
-
- - ) - } - - return renderTyping( - common, - callback - ) - } - - function renderChannelsOperationBlock(callback) { - return renderTyping( - <> -
- channels: -
-
-   userSignedup: -
-
-     address:'user/signedup' -
-
-     messages: -
-
-       userSignedupMessage: -
-
-         $ref:'#/components/messages/UserSignedUp' -
-
- operations: -
-
-   processUserSignups: -
-
-     action:'receive' -
-
-     channel: -
-
-       $ref: '#/channels/userSignedup' -
- - , - callback - ) - } - - function renderUntilMessagePayload(callback) { - return renderTyping( - <> -
- components: -
-
-   messages: -
-
-     UserSignedUp: -
-
-       payload: -
-
-         type: object -
- , - callback - ) - } - - function renderDisplayName(callback) { - const descriptionCallback = () => setShowDisplayNameDescription(true) - - const common = ( - <> -
-         properties: -
-
-           displayName: -
-
-             type: string -
- - ) - - if (showEmail) { - return ( - <> - { common } - } onFinishedTyping={descriptionCallback}> -
-             description: Name of the user -
-
- - ) - } - - return renderTyping( - <> - { common } - , - callback - ) - } - - function renderEmail(callback) { - const descriptionCallback = () => setShowEmailDescription(true) - - const common = ( - <> -
-           email: -
-
-             type: string -
-
-             format: email -
- - ) - - if (showDisplayNameDescription) { - return ( - <> - {common} - } onFinishedTyping={descriptionCallback}> -
-             description: Email of the user -
-
- - ) - } - - return renderTyping( - common, - callback - ) - } - - return ( -
-
-
- - { (showEmailDescription || started) && renderInfoBlock(() => setShowInfo(true)) } - { showInfo && renderChannelsOperationBlock(() => setShowChannelsAndOperation(true)) } - { showChannelsAndOperation && renderUntilMessagePayload(() => setShowUntilMessagePayload(true)) } - { (showUntilMessagePayload || showEmail) && renderDisplayName(() => setShowDisplayName(true)) } - { (showDisplayName || showDisplayNameDescription) && renderEmail(() => setShowEmail(true)) } - -
-
-
- - Play with it! - -

- Open this example on AsyncAPI Studio to get a better taste of the specification. No signup is required! -

- -
- -
-

Account Service 1.0.0

-

- This service is in charge of processing user signups 🚀 -

-
- -
- RECEIVES user/signedup -
- -
-
Accepts the following message:
-
- Payload Object -
-
-
-
displayName
-
-
String
-
Name of the user
-
-
-
-
email
-
-
- String - email -
-
Email of the user
-
-
- -
Additional properties are allowed.
-
- -
-
// Example
-
 
-
{'{'}
-
-   "displayName": "Eve & Chan", -
-
-   "email": "info@asyncapi.io" -
-
{'}'}
-
-
-
-
-
-
-
-
- ) -} - -function Cursor({ className = '' }) { - return ( - - ) -} diff --git a/components/DemoAnimation.tsx b/components/DemoAnimation.tsx new file mode 100644 index 000000000000..0e8affd76280 --- /dev/null +++ b/components/DemoAnimation.tsx @@ -0,0 +1,383 @@ +import { useEffect, useState } from 'react'; +import Typing from 'react-typist-component'; + +import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading'; + +import OpenInStudioButton from './buttons/OpenInStudioButton'; +import ArrowRight from './icons/ArrowRight'; +import MacWindow from './MacWindow'; +import Heading from './typography/Heading'; + +interface IDemoAnimationProps { + className?: string; +} + +interface ICursorProps { + className?: string; +} + +/** + * @description A component that displays a cursor for the typing animation + * @param {string} props.className - The class name for the component + */ +function Cursor({ className = '' }: ICursorProps) { + return ( + + ); +} + +/** + * @description A component that displays a demo animation of an AsyncAPI document + * @param {string} props.className - The class name for the component + */ +export default function DemoAnimation({ className = '' }: IDemoAnimationProps) { + const [started] = useState(true); + const [showInfo, setShowInfo] = useState(false); + const [showChannelsAndOperation, setShowChannelsAndOperation] = useState(false); + const [showUntilMessagePayload, setShowUntilMessagePayload] = useState(false); + const [showDisplayName, setShowDisplayName] = useState(false); + const [showEmail, setShowEmail] = useState(false); + const [showDisplayNameDescription, setShowDisplayNameDescription] = useState(false); + const [showEmailDescription, setShowEmailDescription] = useState(false); + const [finished, setFinished] = useState(false); + const [showControls, setShowControls] = useState(false); + const typingDelay = 60; + + useEffect(() => { + if (finished) { + setTimeout(() => { + setShowControls(true); + }, 2000); + } + }, [finished]); + + /** + * @description classes that displays a cursor for the typing animation + * @param {boolean} condition - The condition to determine which class to apply + */ + function transitionClassNames(condition: boolean) { + return `transition-all duration-500 ease-in-out overflow-hidden ${condition ? 'opacity-100 max-h-auto' : 'max-h-0 opacity-0'}`; + } + + /** + * @description A component that displays a cursor for the typing animation + */ + function renderTyping(children: React.ReactNode, callback: () => void) { + return ( + } onTypingDone={callback}> + {children} + + ); + } + + /** + * @description A component that displays info block + */ + function renderInfoBlock(callback: () => void) { + const descriptionCallback = () => setFinished(true); + + const common = ( + <> +
+ asyncapi: 3.0.0 +
+
+ info: +
+
+   title: Account Service +
+
+   version: 1.0.0 +
+ + + ); + + if (showEmailDescription) { + return ( + <> + {common} + } onTypingDone={descriptionCallback}> +
+   description: This service is in charge of processing + user signups :rocket: +
+
+ + ); + } + + return renderTyping(common, callback); + } + + /** + * @description A component that displays channels and operation block + */ + function renderChannelsOperationBlock(callback: () => void) { + return renderTyping( + <> +
+ channels: +
+
+   userSignedup: +
+
+     address: + 'user/signedup' +
+
+     messages: +
+
+       userSignedupMessage: +
+
+         $ref: + '#/components/messages/UserSignedUp' +
+
+ operations: +
+
+   processUserSignups: +
+
+     action: + 'receive' +
+
+     channel: +
+
+       $ref: + '#/channels/userSignedup' +
+ + , + callback + ); + } + + /** + * @description A component that displays until message payload block + */ + function renderUntilMessagePayload(callback: () => void) { + return renderTyping( + <> +
+ components: +
+
+   messages: +
+
+     UserSignedUp: +
+
+       payload: +
+
+         type: + object +
+ , + callback + ); + } + + /** + * @description A component that displays display name block + */ + function renderDisplayName(callback: () => void) { + const descriptionCallback = () => setShowDisplayNameDescription(true); + + const common = ( + <> +
+         properties: +
+
+ +           displayName: + +
+
+ +             type: + + string +
+ + ); + + if (showEmail) { + return ( + <> + {common} + } onTypingDone={descriptionCallback}> +
+ +             description: + + Name of the user +
+
+ + ); + } + + return renderTyping(<>{common}, callback); + } + + /** + * @description A component that displays email block + */ + function renderEmail(callback: () => void) { + const descriptionCallback = () => setShowEmailDescription(true); + + const common = ( + <> +
+           email: +
+
+ +             type: + + string +
+
+ +             format: + + email +
+ + ); + + if (showDisplayNameDescription) { + return ( + <> + {common} + } onTypingDone={descriptionCallback}> +
+ +             description: + + Email of the user +
+
+ + ); + } + + return renderTyping(common, callback); + } + + return ( +
+
+
+ + {(showEmailDescription || started) && renderInfoBlock(() => setShowInfo(true))} + {showInfo && renderChannelsOperationBlock(() => setShowChannelsAndOperation(true))} + {showChannelsAndOperation && renderUntilMessagePayload(() => setShowUntilMessagePayload(true))} + {(showUntilMessagePayload || showEmail) && renderDisplayName(() => setShowDisplayName(true))} + {(showDisplayName || showDisplayNameDescription) && renderEmail(() => setShowEmail(true))} + +
+
+
+ + Play with it! + +

+ Open this example on AsyncAPI Studio to get a better taste of the specification. No signup is required! +

+ +
+ +
+

Account Service 1.0.0

+

This service is in charge of processing user signups 🚀

+
+ +
+ RECEIVES{' '} + user/signedup +
+ +
+
Accepts the following message:
+
+ Payload{' '} + {' '} + Object +
+
+
+
displayName
+
+
String
+
+ Name of the user +
+
+
+
+
email
+
+
+ String + email +
+
Email of the user
+
+
+ +
Additional properties are allowed.
+
+ +
+ {/* eslint-disable-next-line react/jsx-no-comment-textnodes */} +
// Example
+
 
+
{'{'}
+
+   "displayName":{' '} + "Eve & Chan", +
+
+   "email":{' '} + "info@asyncapi.io" +
+
{'}'}
+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/Feedback.js b/components/Feedback.js deleted file mode 100644 index 1ba34b84ed4f..000000000000 --- a/components/Feedback.js +++ /dev/null @@ -1,109 +0,0 @@ -import { useState, useEffect } from "react"; -import { useRouter } from "next/router"; -import GitHubIssue from "./buttons/GitHubIssue"; - -export default function Feedback(className = '') { - const [submitted, setSubmitted] = useState(false); - const [error, setError] = useState(false); - const [feedback, setFeedback] = useState(''); - const { asPath, pathname } = useRouter(); - - useEffect(() => { - setSubmitted(false); - setError(false); - }, [asPath]) - - const date_stamp = new Date() - const time_stamp = date_stamp.toUTCString(); - - async function handleSubmit(e) { - e.preventDefault(); - const data = { - title: `Feedback on ${asPath} - ${time_stamp}`, - feedback: feedback - } - - fetch("/.netlify/functions/github_discussions", { - method: "POST", - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json', - }, - }).then((response) => { - - if (response.status === 200) { - setSubmitted(true); - } - if(response.status !== 200) { - setError(true); - } - response.json(); - console.log(response); - }).then(data =>{ - console.log(data); - }) - } - - if (submitted) { - return ( -
-
- -
-
- Thank you for your feedback! -
-
- Your contribution has been received and we couldn't be happier. -
- -
- - Follow on GitHub -
-
-
- ) - } - if(error){ - return ( -
-
- -
-
- Oops! Something went wrong... -
-
- We were unable to process your feedback -
- -
- ); - } - return ( -
-
- -
-
- Was this helpful? -
-
- Help us improve the docs by adding your contribution. -
-
-
-
-
-