Conversation
* Update if-nodejs-pr-testing.yml * Revert "Update if-nodejs-pr-testing.yml" This reverts commit d54eec4. * Navbar design, and shape * navbar designed light mode desktop * added dark mode config * added linux banner * fixed icon colors for docs page, made edits to homepage Signed-off-by: Shriya-Chauhan <auroralflower@gmail.com> * docs page changes * changed homepage * changing dutch language json * fix: lint and locale issues * feat: adding color hex codes * chore: removed hsl coloring * fix: changed dark theme names is config --------- Signed-off-by: Shriya-Chauhan <auroralflower@gmail.com>
…he design (#4485) Co-authored-by: Shriya-Chauhan <auroralflower@gmail.com> Co-authored-by: Chan <bot+chan@asyncapi.io>
…ge, tools page, dashboard and community page (#4553)
✅ Deploy Preview for asyncapi-website ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
We require all PRs to follow Conventional Commits specification. |
📝 WalkthroughWalkthroughThis pull request introduces comprehensive dark mode support across the website, adds new community-focused components and pages (including ambassador profiles, events hub, and tooling showcase), refactors existing pages for improved layout and functionality, and updates configuration files to support the new design system. The changes span from low-level component styling to high-level page restructuring. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
|
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-5253--asyncapi-website.netlify.app/ |
|
There was a problem hiding this comment.
Actionable comments posted: 18
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
components/AlgoliaSearch.tsx (1)
288-318:⚠️ Potential issue | 🟠 Major
actionKeyin dependency array causes effect to run on every render.
actionKeyis assigned viaconst actionKey = getActionKey();which creates a new object reference on each render. Adding it to the dependency array means this effect will execute on every render, defeating the purpose of the dependency check.Based on learnings, the original empty dependency array was intentional—the effect only needs to run once since
getActionKey()depends solely onnavigator.userAgent, which is stable.🛠️ Suggested fix: memoize actionKey or restore intentional empty deps
Option 1 (preferred): Memoize actionKey
+ import { useMemo } from 'react'; // ... - const actionKey = getActionKey(); + const actionKey = useMemo(() => getActionKey(), []);Option 2: Restore empty deps with eslint-disable comment
useEffect(() => { if (typeof children === 'function') { setChildren(children({ actionKey })); } else { setChildren(children); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- actionKey is stable (depends only on navigator.userAgent) }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/AlgoliaSearch.tsx` around lines 288 - 318, The effect that computes and sets children is re-running every render because actionKey is recreated by getActionKey() each time; fix by memoizing actionKey with React.useMemo so its reference is stable (e.g., const actionKey = useMemo(() => getActionKey(), [])) and then keep [actionKey, children] in the useEffect dependency array, referencing the symbols getActionKey, actionKey, children, and setChildren to locate the code; alternatively, if you intentionally want it to run once, restore the empty dependency array and add an eslint-disable-next-line comment for exhaustive-deps next to the useEffect.components/Remember.tsx (1)
20-30:⚠️ Potential issue | 🟡 MinorMissing dark mode text color for children content.
The container has
text-gray-900but lacks a dark mode variant. Thechildrencontent (line 30) will inherit this color, causing poor contrast on the dark background.Proposed fix
<div - className={`${className} mb-8 mt-4 rounded bg-secondary-100 dark:bg-dark-background p-4 text-gray-900`} + className={`${className} mb-8 mt-4 rounded bg-secondary-100 dark:bg-dark-background p-4 text-gray-900 dark:text-gray-100`} data-testid='Remember-main' >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/Remember.tsx` around lines 20 - 30, The component's outer div sets text-gray-900 but no dark-mode text color, causing children (rendered via {children}) to be unreadable on dark backgrounds; update the className on the div (where className prop is merged into the template string) to include a dark mode text color such as dark:text-gray-100 (or another appropriate token) so children inherit a contrasting color in dark theme — modify the class list in the Remember component's root div that currently includes text-gray-900 to add the dark: variant.components/Feedback.tsx (2)
82-94:⚠️ Potential issue | 🟡 MinorError state lacks dark mode styling.
Similar to the success state, the error state container uses light-mode-only classes and will appear inconsistent when dark mode is active.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/Feedback.tsx` around lines 82 - 94, The error branch in the Feedback component (the JSX block rendered when error is truthy, identified by the data-testid 'Feedback-error' and the GitHubIssue component) only uses light-mode utility classes (e.g., border-gray-200, text-gray-500) and needs matching dark-mode variants; update the container and inner text/image classes to include appropriate dark: prefixed Tailwind classes (e.g., dark:border-*, dark:bg-*, dark:text-*) mirroring the success state styling so the error card renders correctly in dark mode.
56-80:⚠️ Potential issue | 🟡 MinorSuccess state lacks dark mode styling.
The submitted success state uses light-mode-only classes (
border-gray-200,text-gray-500,bg-black). When a user submits feedback while in dark mode, this view will appear inconsistent with the rest of the UI.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/Feedback.tsx` around lines 56 - 80, The success view rendered when submitted is true lacks dark-mode variants; update the JSX that returns the success block in the Feedback component so the container, text, border and button include Tailwind dark: classes (e.g., add dark:border-<darker>, dark:bg-<>, dark:text-<> equivalents) to mirror light-mode styles in dark theme; specifically adjust the outer div (currently using border-gray-200), the copy lines (text-gray-500), and the GitHub button (bg-black and text-white) to include appropriate dark: variants so the success state respects dark mode styling.components/tools/ToolsDashboard.tsx (3)
50-62:⚠️ Potential issue | 🟡 MinorSame issue: missing dependency array.
This click-outside handler for the category dropdown also lacks a dependency array, causing the same listener churn issue.
Proposed fix
useEffect(() => { const checkIfClickOutside = (event: MouseEvent) => { if (openCategory && categoryRef.current && !categoryRef.current.contains(event.target as Node)) { setopenCategory(false); } }; document.addEventListener('mousedown', checkIfClickOutside); return () => { document.removeEventListener('mousedown', checkIfClickOutside); }; - }); + }, [openCategory]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/tools/ToolsDashboard.tsx` around lines 50 - 62, The useEffect that adds the click-outside listener is missing a dependency array and currently re-attaches on every render; update the effect (function checkIfClickOutside, useEffect) to run only when openCategory changes by adding a dependency array (e.g., [openCategory]) and inside the effect only add the document.addEventListener when openCategory is true, with the same cleanup removing checkIfClickOutside; keep references to categoryRef and setopenCategory as used but do not recreate the listener on every render.
35-47:⚠️ Potential issue | 🟡 MinorMissing dependency array causes listener churn on every render.
This useEffect has no dependency array, so it runs on every render—adding and removing event listeners repeatedly. This is inefficient and could cause subtle timing bugs.
Proposed fix
useEffect(() => { const checkIfClickOutside = (event: MouseEvent) => { if (openFilter && filterRef.current && !filterRef.current.contains(event.target as Node)) { setOpenFilter(false); } }; document.addEventListener('mousedown', checkIfClickOutside); return () => { document.removeEventListener('mousedown', checkIfClickOutside); }; - }); + }, [openFilter]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/tools/ToolsDashboard.tsx` around lines 35 - 47, The effect currently re-attaches the mousedown listener every render because it has no dependency array; update the useEffect surrounding checkIfClickOutside so it only runs when relevant state changes (e.g., add a dependency array of [openFilter]) to prevent listener churn. Locate the useEffect that defines checkIfClickOutside (it references openFilter, filterRef, and calls setOpenFilter) and add the dependency array [openFilter] (or adjust to [] and lift openFilter into a ref if you need the handler to read the latest value without reattaching) so the event listener is not added/removed on every render.
153-166:⚠️ Potential issue | 🟠 MajorRemove
toolsListfrom useEffect dependency; trigger scroll only on hash changes, not filter changes.The scroll effect currently depends on
[toolsList], which updates whenever filters are applied. This causes the page to unexpectedly scroll to a hashed category whenever the user filters—even if they didn't navigate. Track the hash directly instead:Suggested fix
useEffect(() => { const { hash } = window.location; if (hash) { const elementID = decodeURIComponent(hash.slice(1)); const element = toolsList[elementID]?.elementRef!; if (element.current) { document.documentElement.style.scrollPaddingTop = '6rem'; element.current.scrollIntoView({ behavior: 'smooth' }); document.documentElement.style.scrollPaddingTop = '0'; } } }, [router.asPath]); // or track hash separately🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/tools/ToolsDashboard.tsx` around lines 153 - 166, The useEffect that scrolls to a hashed category should not depend on toolsList because filter changes cause unwanted scrolling; update the dependency to track the URL/hash instead (e.g. router.asPath or a derived hash value) so the effect only runs when the location changes, keep the existing logic that decodes the hash, looks up toolsList[elementID]?.elementRef, checks element.current, sets document.documentElement.style.scrollPaddingTop, calls element.current.scrollIntoView, and then resets scrollPaddingTop.components/layout/GenericLayout.tsx (1)
31-32:⚠️ Potential issue | 🟡 MinorRemove the incorrect eslint-disable comment.
The
hideBannerparameter is actually used on line 43 in the className logic (${hideBanner && 'hidden'}), so theunused-imports/no-unused-varsandno-unused-varsdisable comments are misleading and should be removed.Proposed fix
- // eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars - hideBanner = false + hideBanner = false🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/layout/GenericLayout.tsx` around lines 31 - 32, Remove the incorrect eslint-disable comment that silences unused variable rules for the hideBanner prop: delete the line "// eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars" and keep the hideBanner = false default intact; confirm the prop is referenced in the className logic (`${hideBanner && 'hidden'}`) and remove any other misleading unused-vars disables related to hideBanner in the same file (GenericLayout.tsx).
🟡 Minor comments (27)
components/CaseStudyCard.tsx-36-41 (1)
36-41:⚠️ Potential issue | 🟡 MinorPotential nested interactive elements inside Link.
A
Buttoncomponent is rendered inside aLinkwrapper. IfButtonrenders as an<a>tag internally, this creates nested anchor tags causing hydration issues in Next.js. If it renders as a<button>, there's still an accessibility concern with multiple focusable elements.Since the entire card is already clickable via the Link, consider using a visual-only element styled as a button (e.g., a
<span>with button styling) instead of an interactiveButtoncomponent.♻️ Proposed fix using a non-interactive element
<div className='mt-auto'> - <Button - text='Read case study →' - className='w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors' - /> + <span className='block w-full text-center py-2 px-4 rounded-md bg-primary-500 group-hover:bg-primary-600 text-white transition-colors'> + Read case study → + </span> </div>Based on learnings: In Next.js, nested
<a>tags cause hydration issues due to invalid HTML. To fix this, avoid nesting interactive elements insideLinkcomponents.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/CaseStudyCard.tsx` around lines 36 - 41, The card currently wraps a Button inside a Link in CaseStudyCard which can create nested interactive elements and invalid nested anchors; replace the interactive Button usage with a non-interactive element (e.g., a <span> or <div> styled with the same classes and text) or use Button’s non-interactive/as-prop if available so the visual “Read case study →” remains styled but is not a separate focusable control; update the element used in the Button render in CaseStudyCard (the Button component invocation and the surrounding Link) to this non-interactive span/div and keep the same className and text to preserve styling and accessibility.components/navigation/StickyNavbar.tsx-18-28 (1)
18-28:⚠️ Potential issue | 🟡 MinorInitialize scroll-derived state on mount.
isScrolledis only updated on scroll events. When a page loads or is restored already scrolled past 50px, the navbar renders with incorrect styling until the next scroll event. Call the handler once during effect setup to sync initial state with current scroll position.Suggested patch
useEffect(() => { const handleScroll = () => { const scrollTop = window.scrollY; setIsScrolled(scrollTop > 50); // Trigger after scrolling 50px }; + handleScroll(); // Sync initial state with current scroll position window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); // Clean up }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/navigation/StickyNavbar.tsx` around lines 18 - 28, The effect that registers handleScroll doesn't initialize scroll-derived state on mount, so the navbar may render with wrong styling if the page is already past 50px; inside the useEffect that defines handleScroll and calls window.addEventListener('scroll', handleScroll), invoke handleScroll() once immediately after adding the listener (or before) to sync initial state, ensuring setIsScrolled is called with the current window.scrollY on mount and keep the existing cleanup that removes the listener.components/footer/Footer.tsx-16-16 (1)
16-16:⚠️ Potential issue | 🟡 MinorReplace invalid Tailwind token in footer class list.
Line 16 contains
margin: 0 auto, which is raw CSS syntax and won't be processed as a Tailwind utility class. Usemx-autoif horizontal centering is intended.Proposed fix
- <footer className='margin: 0 auto mt-12 dark:bg-dark-footer bg-dark'> + <footer className='mx-auto mt-12 bg-dark dark:bg-dark-footer'>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/footer/Footer.tsx` at line 16, The footer element in the Footer component uses raw CSS token "margin: 0 auto" in its className which Tailwind won't parse; update the footer's class list in Footer.tsx (the <footer> element) to replace "margin: 0 auto" with the Tailwind utility "mx-auto" so the element is horizontally centered (keep the rest of the class names unchanged).public/_redirects-71-72 (1)
71-72:⚠️ Potential issue | 🟡 MinorAvoid introducing a 2-hop redirect chain.
With Line 71 + Line 72 together,
/community/meetingsnow redirects twice. Point it directly to/community/events-and-updatesto reduce latency and crawler hop depth.Suggested update
-/community/meetings /community/events 301! +/community/meetings /community/events-and-updates 301! /community/events /community/events-and-updates 301!🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/_redirects` around lines 71 - 72, Replace the two-hop redirect chain by updating the rule that maps "/community/meetings" so it points directly to "/community/events-and-updates" instead of first redirecting to "/community/events"; update the "/community/meetings" -> "/community/events" entry to "/community/meetings" -> "/community/events-and-updates" (or remove the intermediate rule and ensure only "/community/events" -> "/community/events-and-updates" remains) so requests to "/community/meetings" resolve in one hop.components/icons/CodeBrackets.tsx-4-7 (1)
4-7:⚠️ Potential issue | 🟡 MinorType the icon props explicitly to avoid implicit-
anyrisk in strict mode.The untyped parameter
{ className = '' }will trigger an implicit-any error with"strict": trueenabled. Align with the established icon pattern usingReact.SVGProps<SVGSVGElement>and the spread operator.Proposed diff
/** * `@description` Icon component for Code Brackets */ -export default function IconCodeBrackets({ className = '' }) { +export default function IconCodeBrackets(props: React.SVGProps<SVGSVGElement>) { return ( - <svg className={className} fill='currentColor' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'> + <svg {...props} fill='currentColor' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/icons/CodeBrackets.tsx` around lines 4 - 7, The IconCodeBrackets component's parameter is untyped and will cause implicit-any under strict mode; change the signature to accept props typed as React.SVGProps<SVGSVGElement> (e.g., (props: React.SVGProps<SVGSVGElement>)) and use the spread operator on the <svg> element so consumers can pass className and other SVG props (preserve the default className behavior by allowing props.className || '' when needed and remove the untyped destructuring).components/community/UpcomingEventsSection.tsx-26-30 (1)
26-30:⚠️ Potential issue | 🟡 MinorGuard against SSR:
windowmay be undefined on the server.
window.scrollTowill throw during server-side rendering. Add a guard for client-side execution.Suggested fix
const goToPage = (pageNumber: number) => { setCurrentPage(pageNumber); // Scroll to top of events section - window.scrollTo({ top: 0, behavior: 'smooth' }); + if (typeof window !== 'undefined') { + window.scrollTo({ top: 0, behavior: 'smooth' }); + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/community/UpcomingEventsSection.tsx` around lines 26 - 30, The goToPage function calls window.scrollTo which will throw during SSR; modify goToPage (the function that calls setCurrentPage) to only call window.scrollTo when running on the client by guarding with a runtime check (e.g. typeof window !== 'undefined' or checking if window?.scrollTo exists) so setCurrentPage still runs on server but scrolling only happens in the browser.pages/community/tsc.tsx-147-149 (1)
147-149:⚠️ Potential issue | 🟡 MinorTighten this TSC description sentence.
“Maintainers take committee seats decisions...” reads malformed and is hard to parse on a public-facing card. Please reword this before shipping.
✍️ Possible wording
- Maintainers take committee seats decisions on the direction of the project, including releases, + Maintainers take committee seats and make decisions on the direction of the project, including releases,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/community/tsc.tsx` around lines 147 - 149, The TSC description sentence in pages/community/tsc.tsx is malformed ("Maintainers take committee seats decisions..."); update that paragraph so it reads clearly — e.g., change the sentence to: "Maintainers make decisions about committee seats and the project's direction, including releases, contribution policies, and other strategic matters." Locate and replace the malformed string in the TSC component/JSX where that paragraph is rendered.components/docs/Card.tsx-47-48 (1)
47-48:⚠️ Potential issue | 🟡 MinorChoose one dark border token.
dark:border-primary-500anddark:border-borderboth set the icon container's border color, so one of them is dead and the dark-mode result is accidental. Keep only the intended dark variant here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/docs/Card.tsx` around lines 47 - 48, The className on the Card component sets two conflicting dark-mode border tokens—dark:border-primary-500 and dark:border-border—so remove the unintended one and keep only the intended dark variant; update the JSX in the Card component where className is composed (the element that uses size-12, rounded-lg, text-primary-500, bg-primary-100, etc.) to include only the correct dark:border-* token (either dark:border-primary-500 or dark:border-border) so the icon container has a single deterministic dark-mode border color.pages/roadmap.tsx-543-547 (1)
543-547:⚠️ Potential issue | 🟡 MinorReword the middle goal card copy.
“Just add a goal as frictionless user experience” reads broken and is hard to understand in a prominent section. Please tighten this sentence before release.
✍️ Possible wording
- description='Build your first API in minutes—no prior AsyncAPI knowledge needed. Just add a goal as frictionless user experience. Your idea to production, across all frameworks.' + description='Build your first API in minutes—no prior AsyncAPI knowledge needed. Focus on your business logic while AsyncAPI handles the rest, across all frameworks.'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/roadmap.tsx` around lines 543 - 547, The description prop for the GoalCardRoadmapPage component (icon=IconLightning, title='Effortless API Development') is unclear; update the description string to a concise, grammatically correct sentence that conveys you can create an API quickly without prior AsyncAPI knowledge—e.g., replace "Just add a goal as frictionless user experience" with a tightened sentence like "Add a goal and build your first API in minutes with a frictionless user experience." Ensure you only change the description prop value in the GoalCardRoadmapPage instance.pages/community/tsc.tsx-221-272 (1)
221-272:⚠️ Potential issue | 🟡 MinorExpose which filter chip is active.
These buttons behave like a toggle group, but the selected state is only visible visually. Add
aria-pressedto each button (or switch to tabs witharia-selected) so screen-reader users can tell which filter is on.Apply the same pattern to the remaining filter buttons.♿ Quick fix
<button + aria-pressed={filterType === 'all'} onClick={() => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/community/tsc.tsx` around lines 221 - 272, The filter buttons (the elements that call setFilterType and setCurrentPage and read filterType) are a toggle group but lack accessible state for assistive tech; add an aria-pressed attribute to each button that evaluates to true when filterType matches the button's key (e.g., aria-pressed={filterType === 'all'} for the "All" button, similarly for 'maintainer', 'available', 'company'), ensuring the attribute is applied on the same button elements that call setFilterType/setCurrentPage so screen readers can announce the active filter; apply the same aria-pressed pattern to any remaining filter buttons.pages/roadmap.tsx-515-520 (1)
515-520:⚠️ Potential issue | 🟡 MinorReplace the placeholder hero link.
href='#'creates a focusable control that just jumps to the top of the page. Point this at a real section/resource or render plain text until the destination exists.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/roadmap.tsx` around lines 515 - 520, The TextLink currently uses href='#' which creates a pointless focusable jump; update the TextLink in pages/roadmap.tsx (the TextLink element rendering "Any kind of APIs") to either point to a real destination (e.g., a docs or section route like '/docs/apis' or a valid fragment id such as '#apis' that exists) by replacing the href value, or if no destination exists yet, render it as plain text/non-link (remove the href prop or use a non-anchor component such as a Text/span) so it is not a focusable dead link.components/community/Card.tsx-88-89 (1)
88-89:⚠️ Potential issue | 🟡 MinorDark-mode tagline text color is likely too low-contrast.
On Line [88],
dark:text-gray-900can become hard to read against dark/colored tagline backgrounds.Suggested fix
- className={`flex w-min justify-between rounded-xl p-2 text-center text-xs text-black dark:text-gray-900 ${taglineBg}`} + className={`flex w-min justify-between rounded-xl p-2 text-center text-xs text-black dark:text-gray-100 ${taglineBg}`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/community/Card.tsx` around lines 88 - 89, The tagline's dark-mode text color class (dark:text-gray-900) on the JSX element with data-testid 'Card-lg-tagline' yields poor contrast; update the className used in that element (the one combining taglineBg) to use a higher-contrast dark-mode text color (for example dark:text-white or dark:text-gray-100) so the text remains readable against dark/colored tagline backgrounds, and run a quick contrast check to ensure it meets WCAG AA.components/navigation/NavItem.tsx-81-81 (1)
81-81:⚠️ Potential issue | 🟡 MinorAdd dark-mode text color for button-only nav items.
On Line [81],
text-gray-900can become too dark in dark theme.Suggested fix
- <button type='button' {...attrs} className={`${attrs.className} text-gray-900`}> + <button type='button' {...attrs} className={`${attrs.className} text-gray-900 dark:text-dark-text`}>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/navigation/NavItem.tsx` at line 81, The button-only nav item in NavItem.tsx currently hardcodes "text-gray-900" which is too dark in dark mode; update the button element (the <button ... {...attrs} ...> in NavItem.tsx) to include a dark-mode counterpart (for example add "dark:text-<lighter-color>" alongside "text-gray-900") when composing attrs.className so the merged class string contains both light and dark color utilities; ensure you preserve any existing attrs.className and other classes when updating the className composition.components/dashboard/GoodFirstIssues.tsx-66-78 (1)
66-78:⚠️ Potential issue | 🟡 MinorMark the decorative SVG as non-accessible content.
This icon is visual-only; expose only the text label to screen readers.
Suggested fix
<svg className='w-5 h-5 text-gray-700 dark:text-gray-300 flex-shrink-0' fill='none' viewBox='0 0 24 24' stroke='currentColor' + aria-hidden='true' + focusable='false' >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/dashboard/GoodFirstIssues.tsx` around lines 66 - 78, The SVG in the GoodFirstIssues component is decorative and should be hidden from assistive tech; update the <svg> element in GoodFirstIssues.tsx to include aria-hidden="true" and focusable="false" (and ensure it has no <title>), so screen readers expose only the text label rather than the icon.public/locales/en/landing-page.json-16-16 (1)
16-16:⚠️ Potential issue | 🟡 MinorTypo: Double closing parenthesis in description.
The text contains
(EDA))with an extra closing parenthesis.📝 Proposed fix
- "description": "One of our main goals is to improve the current state of Event Driven Architecture (EDA))", + "description": "One of our main goals is to improve the current state of Event Driven Architecture (EDA)",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/locales/en/landing-page.json` at line 16, The "description" value in the JSON localization entry contains a stray extra closing parenthesis; update the "description" string (the JSON key "description" in public/locales/en/landing-page.json) to remove the extra ')' so it reads "(EDA)" instead of "(EDA))", preserving surrounding text and JSON formatting/quoting.public/locales/en/landing-page.json-45-48 (1)
45-48:⚠️ Potential issue | 🟡 MinorSame typo in sneakpeek.description.
The double closing parenthesis typo is duplicated here.
📝 Proposed fix
"sneakpeek": { "title": "Sneak Peek Into The Real Process", - "description": "One of our main goals is to improve the current state of Event Driven Architecture (EDA))" + "description": "One of our main goals is to improve the current state of Event Driven Architecture (EDA)" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/locales/en/landing-page.json` around lines 45 - 48, The string value for the JSON key sneakpeek.description contains an extra closing parenthesis; update sneakpeek.description to remove the duplicate ')' so the sentence reads correctly (e.g., "Event Driven Architecture (EDA)"); ensure the JSON remains valid after editing the sneakpeek object.components/tools/Filters.tsx-146-151 (1)
146-151:⚠️ Potential issue | 🟡 Minor"Undo Changes" element lacks keyboard accessibility.
The clickable
divhas norole,tabIndex, or keyboard event handler, making it inaccessible to keyboard users.♿ Proposed fix for accessibility
<div - className='mb-0 flex cursor-pointer gap-0.5 text-xs hover:underline text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors' + className='mb-0 flex cursor-pointer gap-0.5 text-xs hover:underline text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 rounded' onClick={undoChanges} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') undoChanges(); + }} + role='button' + tabIndex={0} > Undo Changes </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/tools/Filters.tsx` around lines 146 - 151, The "Undo Changes" clickable div in Filters.tsx lacks keyboard accessibility; update the element that calls undoChanges to be focusable and operable via keyboard by adding tabIndex={0}, role="button", and a keyDown handler that invokes undoChanges on Enter or Space, and ensure the onClick remains. Reference the existing onClick={undoChanges} and the undoChanges function so you add the keyboard handler alongside the current click behavior and preserve the visual styles and ARIA semantics.components/MDX/MDX.tsx-204-209 (1)
204-209:⚠️ Potential issue | 🟡 Minor
h5heading is missingtext-gray-900for light mode.Unlike other headings (
h1-h4,h6) which includetext-gray-900for consistent light-mode styling,h5omits it. This may cause inconsistent text color in light mode.🔧 Proposed fix
h5: (props: React.HTMLProps<HTMLHeadingElement>) => ( <h5 {...props} - className={`${props.className || ''} text-md my-4 font-heading dark:text-dark-heading font-bold antialiased`} + className={`${props.className || ''} text-md my-4 font-heading dark:text-dark-heading text-gray-900 font-bold antialiased`} /> ),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/MDX/MDX.tsx` around lines 204 - 209, The h5 renderer in the MDX components is missing the light-mode text color class; update the h5 mapping (the h5: (...) arrow component) to include "text-gray-900" in its className string (matching h1–h4 and h6) so the heading uses the same light-mode color while preserving existing classes like "dark:text-dark-heading", "font-heading", and "font-bold".components/DarkModeToggle.tsx-6-18 (1)
6-18:⚠️ Potential issue | 🟡 MinorUse camelCase
strokeWidthinstead ofstroke-widthin JSX.React requires camelCase for SVG attributes. Using
stroke-widthwill generate a warning and may not apply the attribute correctly.🔧 Proposed fix
const SunIcon = ({ className = 'w-5 h-5' }) => ( <svg className={className} fill='none' stroke='white' - stroke-width='2' + strokeWidth='2' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/DarkModeToggle.tsx` around lines 6 - 18, The SunIcon component uses a non-camelCase SVG attribute ("stroke-width") which causes React warnings; update the SVG in the SunIcon function to use the camelCase prop name "strokeWidth" instead of "stroke-width" (keep the same value '2' and leave other props like className, fill, stroke, viewBox, xmlns unchanged) so the attribute is applied correctly in JSX.styles/globals.css-400-405 (1)
400-405:⚠️ Potential issue | 🟡 Minor
border-radiusdoesn't work withborder-image.The
border-radius: 16pxwill have no effect because CSSborder-imageoverrides the border rendering entirely, ignoring border-radius. The corners will appear square.If rounded corners are needed, consider using a pseudo-element approach or a gradient background with a transparent inner box instead.
Alternative approach using pseudo-element
.dark .gradient-border { position: relative; border-radius: 16px; background: var(--dark-card-bg); /* your card background */ } .dark .gradient-border::before { content: ''; position: absolute; inset: -2px; border-radius: 18px; /* slightly larger to account for border width */ background: linear-gradient(225deg, `#AD20E2` 0%, `#2DCCFD` 100%); z-index: -1; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@styles/globals.css` around lines 400 - 405, The CSS rule for .dark .gradient-border uses border-image which prevents border-radius from taking effect; replace the current approach by removing border-image and implementing the gradient border via a background/pseudo-element: keep border-radius: 16px on .dark .gradient-border, use a positioned ::before (or ::after) pseudo-element behind the element sized to simulate the border with the linear-gradient(225deg, `#AD20E2` 0%, `#2DCCFD` 100%) and slightly larger border-radius, or alternatively use a gradient background with an inner transparent box to achieve rounded corners while preserving the gradient border effect.pages/community/ambassadors/index.tsx-251-254 (1)
251-254:⚠️ Potential issue | 🟡 MinorAvoid using array index as React key.
Using
indexas the key can cause issues with component state and reconciliation if the list is reordered or filtered. Useambassador.githubwhich is guaranteed to be unique.Suggested fix
{currentAmbassadors.map((ambassador: Ambassador, index: number) => ( <div - key={index} + key={ambassador.github} className='group relative bg-white dark:bg-dark-background rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-xl transition-all duration-300 hover:-translate-y-1' data-testid='Ambassadors-members' >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/community/ambassadors/index.tsx` around lines 251 - 254, The list is using array index as the React key in the currentAmbassadors.map callback which can break reconciliation; update the key prop on the mapped element (inside currentAmbassadors.map in pages/community/ambassadors/index.tsx) to use a stable unique identifier such as ambassador.github (e.g., key={ambassador.github}); if github might be missing, fall back to another stable property on the Ambassador object, but do not use the array index.pages/community/ambassadors/index.tsx-174-179 (1)
174-179:⚠️ Potential issue | 🟡 MinorAdd
titleattribute to iframe for accessibility.The YouTube iframe is missing a
titleattribute, which is required for accessibility (WCAG 2.4.2). Screen readers use this to describe the iframe content.Suggested fix
<iframe src={`https://www.youtube.com/embed/${AMBASSADORS_CONFIG.YOUTUBE_VIDEO_ID}`} allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share' className='absolute top-0 left-0 w-full h-full' data-testid='Ambassadors-Iframe' + title='AsyncAPI Ambassador Program Overview Video' ></iframe>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/community/ambassadors/index.tsx` around lines 174 - 179, The iframe embedding the YouTube video (the element using AMBASSADORS_CONFIG.YOUTUBE_VIDEO_ID and data-testid='Ambassadors-Iframe') lacks a title attribute required for accessibility; add a descriptive title prop to the iframe (for example: title={`Ambassadors video - ${AMBASSADORS_CONFIG.YOUTUBE_VIDEO_ID}`} or a clear static string like "Ambassadors YouTube video") so screen readers can identify the iframe content.components/sponsors/PlatinumSponsors.tsx-31-31 (1)
31-31:⚠️ Potential issue | 🟡 MinorReplace
dark:bg-dark-textwithdark:bg-dark-cardordark:bg-dark-background.The
dark-texttoken is defined as#B8B9C0(a text color), not a background color. Using it as a background produces incorrect styling in dark mode. Usedark:bg-dark-card(matching other card components throughout the codebase) ordark:bg-dark-backgroundinstead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/sponsors/PlatinumSponsors.tsx` at line 31, The className on the sponsor card in PlatinumSponsors uses the text color token dark:bg-dark-text incorrectly; update the JSX in the PlatinumSponsors component so that the className replaces dark:bg-dark-text with a background token such as dark:bg-dark-card (or dark:bg-dark-background) to match other card components and ensure proper dark-mode background styling for the element that uses sponsor?.imageClass.components/community/ToolingCard.tsx-56-57 (1)
56-57:⚠️ Potential issue | 🟡 MinorUse a unique SVG gradient id per card instance.
Line 56 hardcodes
id='blueGradient'and Line 79 referencesurl(#blueGradient). Multiple cards on the same page can collide on that id and render the wrong gradient.🎨 Proposed fix
export default function ToolingCard({ name, description, badge, language, link, isShuffling }: ToolingCardProps) { + const gradientId = `tooling-blue-gradient-${name.toLowerCase().replace(/\s+/g, '-')}`; const CardContent = ( @@ - <linearGradient id='blueGradient' x1='20%' y1='80%' x2='80%' y2='20%'> + <linearGradient id={gradientId} x1='20%' y1='80%' x2='80%' y2='20%'> @@ - fill='url(`#blueGradient`)' + fill={`url(#${gradientId})`}Also applies to: 79-80
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/community/ToolingCard.tsx` around lines 56 - 57, The SVG gradient id is hardcoded as 'blueGradient' in the ToolingCard component and referenced via url(`#blueGradient`), which will collide when multiple cards render; update ToolingCard to generate a unique gradient id per instance (e.g., React.useId() or a passed-in prop) and use that unique id for the linearGradient element and its corresponding url(...) references (replace 'blueGradient' occurrences with the generated id) so each card uses its own SVG gradient.pages/blog/index.tsx-107-115 (1)
107-115:⚠️ Potential issue | 🟡 MinorClamp
currentPagebefore slicing.When a user is on a later page and switches to a tab/filter with fewer results,
currentPostsbecomes empty for one render and the “No post matches your filter” state flashes before the effect resets page 1. Clamp the derived page or resetcurrentPagein the same event that changes the filter/tab.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/blog/index.tsx` around lines 107 - 115, The derived slicing uses currentPage that can be out of range after a tab/filter change, causing currentPosts to be empty briefly; clamp currentPage before slicing by computing a safePage = clamp(currentPage, 1, totalPages) and use safePage when computing indexOfFirstPost/indexOfLastPost/currentPosts (symbols: totalPages, currentPage, currentPosts, filteredByTab, postsPerPage), and update state atomically by calling setCurrentPage(safePage) inside the same useEffect that watches activeTab/posts/postsPerPage so the state and slice remain consistent and avoid the flash.pages/community/index.tsx-245-246 (1)
245-246:⚠️ Potential issue | 🟡 MinorThis copy is already stale.
On March 18, 2026, this card still asks users to help with “2024” goals. Please remove the hard-coded year or source it from content/config so the
/communityhub does not age out immediately.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/community/index.tsx` around lines 245 - 246, The paragraph text currently hard-codes "2024" in the Paragraph component (ParagraphTypeStyle.sm) and should be updated to avoid staleness; replace the literal year with a dynamic source—either pull the year from a content/config value (e.g., a community goals config key) or compute it at render time (e.g., using the current year) and interpolate that value into the string so the message stays up-to-date; update the text in the Paragraph element near where ParagraphTypeStyle.sm is used.pages/casestudies/index.tsx-251-256 (1)
251-256:⚠️ Potential issue | 🟡 MinorDon’t fall back to
#for missing resources.
adopter.resources[0]?.link || '#'still renders a clickable CTA but just reopens the current page when a row has no resource. SinceButtonalready supportshrefelsewhere in this component, only render it when a real URL exists and let the button own the link.♻️ Suggested fix
- <Link href={adopter.resources[0]?.link || '#'} target='_blank' rel='noopener noreferrer'> - <Button - text='Learn More' - className='bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 w-full sm:w-auto' - /> - </Link> + {adopter.resources[0]?.link && ( + <Button + text='Learn More' + href={adopter.resources[0].link} + target='_blank' + rel='noopener noreferrer' + className='bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 w-full sm:w-auto' + /> + )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/casestudies/index.tsx` around lines 251 - 256, Replace the current fallback '#' behaviour by only rendering the link/button when a real URL exists: check adopter.resources[0]?.link and if truthy render <Link href={adopter.resources[0].link} ...><Button ... /></Link>; otherwise render the Button without an href or render a disabled/secondary Button (do not use '#' as href). Update the code around Link and Button to let Button own the href when supported (pass href={adopter.resources[0].link} to Button) or omit the link wrapper entirely when no valid URL is present.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c414bbc5-6599-44dc-9a55-d7629075715d
⛔ Files ignored due to path filters (1)
public/img/social/woman.jpgis excluded by!**/*.jpg
📒 Files selected for processing (146)
.gitignorecomponents/AlgoliaSearch.tsxcomponents/Calendar.tsxcomponents/CaseStudyCard.tsxcomponents/CaseTOC.tsxcomponents/DarkModeToggle.tsxcomponents/Feedback.tsxcomponents/Hero.tsxcomponents/MDX/MDX.tsxcomponents/Pagination.tsxcomponents/Remember.tsxcomponents/SneakPeek.tsxcomponents/TOC.tsxcomponents/buttons/DocsButton.tsxcomponents/buttons/GithubButton.tsxcomponents/buttons/GoogleCalendarButton.tsxcomponents/buttons/ICSFileButton.tsxcomponents/campaigns/AnnouncementBanner.tsxcomponents/campaigns/AnnouncementHero.tsxcomponents/campaigns/ConferenceBanner.tsxcomponents/campaigns/LinuxBanner.tsxcomponents/community/Card.tsxcomponents/community/FeatureCard.tsxcomponents/community/Header.tsxcomponents/community/HomeCard.tsxcomponents/community/TSCMemberCard.tsxcomponents/community/ToolingCard.tsxcomponents/community/ToolingsShowcase.tsxcomponents/community/UpcomingEventsSection.tsxcomponents/dashboard/GoodFirstIssues.tsxcomponents/dashboard/GoodFirstIssuesTip.tsxcomponents/dashboard/Header.tsxcomponents/dashboard/table/Filters.tsxcomponents/dashboard/table/Row.tsxcomponents/dashboard/table/Table.tsxcomponents/data/buckets.tscomponents/docs/Card.tsxcomponents/features/index.tsxcomponents/footer/Footer.tsxcomponents/footer/FooterList.tscomponents/form/Select.tsxcomponents/icons/Ambassador.tsxcomponents/icons/ArrowRightStroke.tsxcomponents/icons/BadgeCheckmark.tsxcomponents/icons/Book.tsxcomponents/icons/Clipboard.tsxcomponents/icons/CodeBrackets.tsxcomponents/icons/CommunityDriven.tsxcomponents/icons/Contributing.tsxcomponents/icons/Dashboard.tsxcomponents/icons/Document.tsxcomponents/icons/Explorer.tsxcomponents/icons/ExternalLink.tsxcomponents/icons/Gear.tsxcomponents/icons/GettingStarted.tsxcomponents/icons/GithubOrganization.tsxcomponents/icons/Home.tsxcomponents/icons/LightBulb.tsxcomponents/icons/Lightning.tsxcomponents/icons/Microphone.tsxcomponents/icons/Migration.tsxcomponents/icons/NavItemDropdown.tsxcomponents/icons/Newsroom.tsxcomponents/icons/OpenSource.tsxcomponents/icons/Play.tsxcomponents/icons/Plus.tsxcomponents/icons/PlusSign.tsxcomponents/icons/ProductionReady.tsxcomponents/icons/Star.tsxcomponents/icons/TSC.tsxcomponents/icons/Terminal.tsxcomponents/icons/UseCases.tsxcomponents/icons/Users.tsxcomponents/icons/UsersGroup.tsxcomponents/icons/Video.tsxcomponents/layout/BlogLayout.tsxcomponents/layout/CommunityLayout.tsxcomponents/layout/DocsLayout.tsxcomponents/layout/GenericLayout.tsxcomponents/layout/GenericPostLayout.tsxcomponents/layout/GenericWideLayout.tsxcomponents/layout/Layout.tsxcomponents/logos/AsyncAPILogo.tsxcomponents/navigation/BlogPostItem.tsxcomponents/navigation/DocsNav.tsxcomponents/navigation/DocsNavItem.tsxcomponents/navigation/DocsNavWrapper.tsxcomponents/navigation/EventPostItem.tsxcomponents/navigation/Filter.tsxcomponents/navigation/FlyoutMenu.tsxcomponents/navigation/MenuBlocks.tsxcomponents/navigation/MobileNavMenu.tsxcomponents/navigation/NavBar.tsxcomponents/navigation/NavItem.tsxcomponents/navigation/StickyNavbar.tsxcomponents/navigation/SubCategoryDocsNav.tsxcomponents/navigation/communityItems.tsxcomponents/navigation/learningItems.tsxcomponents/newsroom/NewsroomBlogPosts.tsxcomponents/newsroom/NewsroomSection.tsxcomponents/newsroom/NewsroomYoutube.tsxcomponents/newsroom/YouTubeCard.tsxcomponents/roadmap/GoalCardRoadmapPage.tsxcomponents/roadmap/RoadmapColumn.tsxcomponents/roadmap/RoadmapItem.tsxcomponents/roadmap/RoadmapPill.tsxcomponents/sponsors/DiamondSponsors.tsxcomponents/sponsors/PlatinumSponsors.tsxcomponents/tools/CategoryDropdown.tsxcomponents/tools/Checkbox.tsxcomponents/tools/Filters.tsxcomponents/tools/FiltersDisplay.tsxcomponents/tools/Toggle.tsxcomponents/tools/ToolsCard.tsxcomponents/tools/ToolsDashboard.tsxcomponents/tools/ToolsList.tsxcomponents/typography/Heading.tsxcomponents/typography/Paragraph.tsxcomponents/typography/TextLink.tsxconfig/community-toolings-showcase.jsonpages/[lang]/index.tsxpages/_app.tsxpages/_document.tsxpages/blog/index.tsxpages/casestudies/[id].tsxpages/casestudies/index.tsxpages/community/ambassadors/index.tsxpages/community/board.tsxpages/community/dashboard.tsxpages/community/events-and-updates.tsxpages/community/index.tsxpages/community/tsc.tsxpages/roadmap.tsxpages/tools/index.tsxpublic/_redirectspublic/img/social/community-3.webppublic/img/social/communitypage.webppublic/img/social/slack-ss.webppublic/img/social/slack.webppublic/locales/de/landing-page.jsonpublic/locales/en/landing-page.jsonstyles/globals.csstailwind.config.tstypes/components/navigation/BlogPostType.tstypes/pages/community/Community.tstypes/typography/Heading.ts
| <div className='h-full bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'> | ||
| <div className='flex items-center justify-center h-20 mb-6'> | ||
| <img src={study.company.logo} alt={study.company.name} className='max-h-16 max-w-full object-contain' /> | ||
| </div> | ||
|
|
||
| <Paragraph typeStyle={ParagraphTypeStyle.md} className='text-gray-600 dark:text-gray-400 mb-6 line-clamp-4'> | ||
| {study.company.description} | ||
| </Paragraph> | ||
|
|
||
| <div className='mt-auto'> | ||
| <Button | ||
| text='Read case study →' | ||
| className='w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors' | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
mt-auto will not push the button to the bottom without a flex container.
The mt-auto class on line 36 is intended to push the button to the bottom of the card, but it only works in a flex column layout. The parent div on line 27 lacks flex flex-col, so the button won't be anchored at the bottom when card heights vary.
🐛 Proposed fix to enable flex layout
- <div className='h-full bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'>
+ <div className='h-full flex flex-col bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className='h-full bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'> | |
| <div className='flex items-center justify-center h-20 mb-6'> | |
| <img src={study.company.logo} alt={study.company.name} className='max-h-16 max-w-full object-contain' /> | |
| </div> | |
| <Paragraph typeStyle={ParagraphTypeStyle.md} className='text-gray-600 dark:text-gray-400 mb-6 line-clamp-4'> | |
| {study.company.description} | |
| </Paragraph> | |
| <div className='mt-auto'> | |
| <Button | |
| text='Read case study →' | |
| className='w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors' | |
| /> | |
| </div> | |
| <div className='h-full flex flex-col bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'> | |
| <div className='flex items-center justify-center h-20 mb-6'> | |
| <img src={study.company.logo} alt={study.company.name} className='max-h-16 max-w-full object-contain' /> | |
| </div> | |
| <Paragraph typeStyle={ParagraphTypeStyle.md} className='text-gray-600 dark:text-gray-400 mb-6 line-clamp-4'> | |
| {study.company.description} | |
| </Paragraph> | |
| <div className='mt-auto'> | |
| <Button | |
| text='Read case study →' | |
| className='w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors' | |
| /> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/CaseStudyCard.tsx` around lines 27 - 41, The container div that
wraps the card content in CaseStudyCard has mt-auto on the button wrapper but is
not a flex column, so the button won't be pushed to the bottom; update the root
card container (the div with classes 'h-full bg-white dark:bg-dark-background
border ... p-6') to use a flex column layout (add 'flex flex-col') so the child
div with 'mt-auto' will anchor the Button to the card bottom.
| src={member.github ? `https://www.github.com/${member.github}.png` : ''} | ||
| alt={member.name} | ||
| className='w-16 h-16 rounded-full' | ||
| /> | ||
| <div className='flex-1 min-w-0'> | ||
| <h3 className='text-lg font-bold text-gray-900 dark:text-white truncate'>{member.name}</h3> | ||
| <p className='text-sm text-gray-600 dark:text-gray-400'> | ||
| {member.availableForHire ? 'Available for hire' : member.company || 'Individual Member'} | ||
| </p> | ||
| </div> | ||
| {member.availableForHire && ( | ||
| <span className='inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300'> | ||
| Available | ||
| </span> | ||
| )} | ||
| </div> | ||
|
|
||
| {member.repos && Array.isArray(member.repos) && member.repos.length > 0 && ( | ||
| <div className='mb-4'> | ||
| <p className='text-xs text-gray-500 dark:text-gray-300 mb-2'>Maintainer of:</p> | ||
| <div className='flex flex-wrap gap-2'> | ||
| {member.repos.slice(0, 2).map((repoName: string) => ( | ||
| <span | ||
| key={repoName} | ||
| className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300' | ||
| > | ||
| {repoName} | ||
| </span> | ||
| ))} | ||
| {member.repos.length > 2 && ( | ||
| <span className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'> | ||
| +{member.repos.length - 2} | ||
| </span> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className='flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700'> | ||
| {member.github && ( | ||
| <a | ||
| href={`https://www.github.com/${member.github}`} | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| aria-label={`${member.name || 'Member'} on GitHub`} | ||
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | ||
| > | ||
| <GitHubIcon className='h-5 w-5' /> | ||
| </a> | ||
| )} | ||
| {member.twitter && ( | ||
| <a | ||
| href={`https://www.twitter.com/${member.twitter}`} | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| aria-label={`${member.name || 'Member'} on Twitter`} | ||
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | ||
| > | ||
| <TwitterIcon className='h-5 w-5' /> | ||
| </a> | ||
| )} | ||
| {member.linkedin && ( | ||
| <a | ||
| href={`https://www.linkedin.com/in/${member.linkedin}`} | ||
| target='_blank' |
There was a problem hiding this comment.
Normalize social fields before building profile/avatar URLs.
The current construction assumes raw usernames. If these fields already contain full URLs, Line [63] and Lines [104, 115, 126] generate broken URLs.
Suggested fix
export default function TSCMemberCard({ member }: TSCMemberCardProps) {
+ const toProfileUrl = (value: string | undefined, base: string) =>
+ !value ? '' : value.startsWith('http') ? value : `${base}/${value}`;
+
+ const githubUrl = toProfileUrl(member.github, 'https://www.github.com');
+ const twitterUrl = toProfileUrl(member.twitter, 'https://www.twitter.com');
+ const linkedinUrl = toProfileUrl(member.linkedin, 'https://www.linkedin.com/in');
+ const githubUsername = githubUrl ? githubUrl.split('/').filter(Boolean).pop() : '';
+
return (
@@
- <img
- src={member.github ? `https://www.github.com/${member.github}.png` : ''}
+ <img
+ src={githubUsername ? `https://www.github.com/${githubUsername}.png` : ''}
alt={member.name}
className='w-16 h-16 rounded-full'
/>
@@
- {member.github && (
+ {githubUrl && (
<a
- href={`https://www.github.com/${member.github}`}
+ href={githubUrl}
@@
- {member.twitter && (
+ {twitterUrl && (
<a
- href={`https://www.twitter.com/${member.twitter}`}
+ href={twitterUrl}
@@
- {member.linkedin && (
+ {linkedinUrl && (
<a
- href={`https://www.linkedin.com/in/${member.linkedin}`}
+ href={linkedinUrl}Based on learnings: In AsyncAPI community data, TSC github is required and data processing can convert GitHub usernames to full URLs.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| src={member.github ? `https://www.github.com/${member.github}.png` : ''} | |
| alt={member.name} | |
| className='w-16 h-16 rounded-full' | |
| /> | |
| <div className='flex-1 min-w-0'> | |
| <h3 className='text-lg font-bold text-gray-900 dark:text-white truncate'>{member.name}</h3> | |
| <p className='text-sm text-gray-600 dark:text-gray-400'> | |
| {member.availableForHire ? 'Available for hire' : member.company || 'Individual Member'} | |
| </p> | |
| </div> | |
| {member.availableForHire && ( | |
| <span className='inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300'> | |
| Available | |
| </span> | |
| )} | |
| </div> | |
| {member.repos && Array.isArray(member.repos) && member.repos.length > 0 && ( | |
| <div className='mb-4'> | |
| <p className='text-xs text-gray-500 dark:text-gray-300 mb-2'>Maintainer of:</p> | |
| <div className='flex flex-wrap gap-2'> | |
| {member.repos.slice(0, 2).map((repoName: string) => ( | |
| <span | |
| key={repoName} | |
| className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300' | |
| > | |
| {repoName} | |
| </span> | |
| ))} | |
| {member.repos.length > 2 && ( | |
| <span className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'> | |
| +{member.repos.length - 2} | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| <div className='flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700'> | |
| {member.github && ( | |
| <a | |
| href={`https://www.github.com/${member.github}`} | |
| target='_blank' | |
| rel='noreferrer' | |
| aria-label={`${member.name || 'Member'} on GitHub`} | |
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | |
| > | |
| <GitHubIcon className='h-5 w-5' /> | |
| </a> | |
| )} | |
| {member.twitter && ( | |
| <a | |
| href={`https://www.twitter.com/${member.twitter}`} | |
| target='_blank' | |
| rel='noreferrer' | |
| aria-label={`${member.name || 'Member'} on Twitter`} | |
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | |
| > | |
| <TwitterIcon className='h-5 w-5' /> | |
| </a> | |
| )} | |
| {member.linkedin && ( | |
| <a | |
| href={`https://www.linkedin.com/in/${member.linkedin}`} | |
| target='_blank' | |
| export default function TSCMemberCard({ member }: TSCMemberCardProps) { | |
| const toProfileUrl = (value: string | undefined, base: string) => | |
| !value ? '' : value.startsWith('http') ? value : `${base}/${value}`; | |
| const githubUrl = toProfileUrl(member.github, 'https://www.github.com'); | |
| const twitterUrl = toProfileUrl(member.twitter, 'https://www.twitter.com'); | |
| const linkedinUrl = toProfileUrl(member.linkedin, 'https://www.linkedin.com/in'); | |
| const githubUsername = githubUrl ? githubUrl.split('/').filter(Boolean).pop() : ''; | |
| return ( | |
| <> | |
| <div className='flex items-start gap-4 mb-4'> | |
| <img | |
| src={githubUsername ? `https://www.github.com/${githubUsername}.png` : ''} | |
| alt={member.name} | |
| className='w-16 h-16 rounded-full' | |
| /> | |
| <div className='flex-1 min-w-0'> | |
| <h3 className='text-lg font-bold text-gray-900 dark:text-white truncate'>{member.name}</h3> | |
| <p className='text-sm text-gray-600 dark:text-gray-400'> | |
| {member.availableForHire ? 'Available for hire' : member.company || 'Individual Member'} | |
| </p> | |
| </div> | |
| {member.availableForHire && ( | |
| <span className='inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300'> | |
| Available | |
| </span> | |
| )} | |
| </div> | |
| {member.repos && Array.isArray(member.repos) && member.repos.length > 0 && ( | |
| <div className='mb-4'> | |
| <p className='text-xs text-gray-500 dark:text-gray-300 mb-2'>Maintainer of:</p> | |
| <div className='flex flex-wrap gap-2'> | |
| {member.repos.slice(0, 2).map((repoName: string) => ( | |
| <span | |
| key={repoName} | |
| className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300' | |
| > | |
| {repoName} | |
| </span> | |
| ))} | |
| {member.repos.length > 2 && ( | |
| <span className='inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'> | |
| {member.repos.length - 2} | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| <div className='flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700'> | |
| {githubUrl && ( | |
| <a | |
| href={githubUrl} | |
| target='_blank' | |
| rel='noreferrer' | |
| aria-label={`${member.name || 'Member'} on GitHub`} | |
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | |
| > | |
| <GitHubIcon className='h-5 w-5' /> | |
| </a> | |
| )} | |
| {twitterUrl && ( | |
| <a | |
| href={twitterUrl} | |
| target='_blank' | |
| rel='noreferrer' | |
| aria-label={`${member.name || 'Member'} on Twitter`} | |
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | |
| > | |
| <TwitterIcon className='h-5 w-5' /> | |
| </a> | |
| )} | |
| {linkedinUrl && ( | |
| <a | |
| href={linkedinUrl} | |
| target='_blank' | |
| rel='noreferrer' | |
| aria-label={`${member.name || 'Member'} on LinkedIn`} | |
| className='text-gray-600 dark:text-gray-400 hover:!text-primary-500 dark:hover:!text-primary-500 transition-colors' | |
| > | |
| <LinkedInIcon className='h-5 w-5' /> | |
| </a> | |
| )} | |
| </div> | |
| </> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/community/TSCMemberCard.tsx` around lines 63 - 127, TSCMemberCard
builds avatar and social hrefs directly from
member.github/member.twitter/member.linkedin which breaks if fields are already
full URLs; add small normalization helpers (e.g., normalizeGithubUrl,
normalizeTwitterUrl, normalizeLinkedInUrl or a single normalizeProfileUrl) and
use them where the image src and anchor hrefs are composed (the <img src=...>
and the hrefs for GitHub/Twitter/LinkedIn in TSCMemberCard) so they: 1) return
the input unchanged if it already looks like a URL (starts with http:// or
https://), 2) strip leading "@" or extra path segments and construct the correct
platform URL otherwise (github -> https://github.com/USER or avatar
https://github.com/USER.png, twitter -> https://twitter.com/USER, linkedin ->
https://www.linkedin.com/in/USER). Ensure these helpers are called for the
avatar src and each anchor href in TSCMemberCard.
| <a target='_blank' rel='noreferrer' href={`https://github.com/${item.resourcePath}`} className='block group'> | ||
| <div className='rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-dark-background p-5 transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 hover:border-primary-300 dark:hover:border-primary-600 cursor-pointer'> | ||
| <div className='flex justify-between items-start gap-4'> | ||
| <div className='flex flex-col gap-3 flex-1 min-w-0'> | ||
| <div className='flex items-center gap-2'> | ||
| <div | ||
| className={`flex items-center justify-center w-5 h-5 rounded ${item.isPR ? 'bg-blue-100 dark:bg-blue-900/30' : 'bg-green-100 dark:bg-green-900/30'}`} | ||
| > | ||
| <img | ||
| alt='issue or pull-request icon' | ||
| className='size-3 dark:invert dark:opacity-80' | ||
| src={item.isPR ? '/img/illustrations/icons/pull-request.svg' : '/img/illustrations/icons/issue.svg'} | ||
| data-testid='Row-img-issue' | ||
| /> | ||
| </div> | ||
| <a | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| onClick={(e) => e.stopPropagation()} | ||
| className='text-xs font-medium lowercase text-gray-600 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 transition-colors' | ||
| href={`https://github.com/${item.repo}`} | ||
| data-testid='Row-github-redirect' | ||
| > | ||
| {item.repo} | ||
| </a> |
There was a problem hiding this comment.
Nested anchor tags will cause hydration errors.
The outer <a> tag (line 18) wraps an inner <a> tag (lines 33-42), which is invalid HTML. This will cause React hydration issues in Next.js. Based on learnings, nested <a> tags should be avoided by replacing inner anchors with non-interactive elements like <span> with click handlers.
Suggested fix using span with click handler
- <a
- target='_blank'
- rel='noreferrer'
- onClick={(e) => e.stopPropagation()}
- className='text-xs font-medium lowercase text-gray-600 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 transition-colors'
- href={`https://github.com/${item.repo}`}
- data-testid='Row-github-redirect'
- >
+ <span
+ role='link'
+ tabIndex={0}
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ window.open(`https://github.com/${item.repo}`, '_blank', 'noopener,noreferrer');
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ e.stopPropagation();
+ window.open(`https://github.com/${item.repo}`, '_blank', 'noopener,noreferrer');
+ }
+ }}
+ className='text-xs font-medium lowercase text-gray-600 dark:text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 transition-colors cursor-pointer'
+ data-testid='Row-github-redirect'
+ >
{item.repo}
- </a>
+ </span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/dashboard/table/Row.tsx` around lines 18 - 42, The outer anchor in
the Row component wraps an inner anchor
(href={`https://github.com/${item.repo}`} with
data-testid='Row-github-redirect'), causing invalid nested <a> tags and
hydration errors; replace the inner <a> with a non-anchor element (e.g., a
<span> or <button>-like element) that preserves behavior: keep the onClick
handler that calls e.stopPropagation() and programmatically opens the repo URL
in a new tab (window.open or similar), retain the same classes and data-testid
('Row-github-redirect'), and add keyboard accessibility (role="link" and
tabIndex=0 plus keypress handler for Enter) so the visual and interaction
behavior of item.repo remains identical without nested anchors.
| export default function IconBook({ className = '' }) { | ||
| return ( | ||
| <svg className={className} fill='none' viewBox='0 0 24 24' stroke='currentColor' xmlns='http://www.w3.org/2000/svg'> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify TypeScript strictness and potential implicit-any exposure.
fd -HI '^tsconfig.*\.json$' | while read -r f; do
echo "---- $f ----"
rg -nP '"(strict|noImplicitAny)"\s*:\s*(true|false)' "$f"
done
# Confirm untyped destructured parameter in this icon file.
rg -nP --type=tsx 'export default function IconBook\(\{[^)]*\}\)' components/icons/Book.tsxRepository: asyncapi/website
Length of output: 151
🏁 Script executed:
#!/bin/bash
# Check other icon components to understand typing patterns
echo "=== Checking other icon components ==="
fd -t f '\.(tsx|ts)$' components/icons/ | head -10 | while read f; do
echo "--- $f ---"
head -10 "$f"
done
echo -e "\n=== Checking how many icon components exist and their signatures ==="
fd -t f '\.(tsx|ts)$' components/icons/ | wc -l
echo -e "\n=== Sample of export signatures in icon files ==="
rg -h '^export (default )?function Icon' components/icons/ | head -5Repository: asyncapi/website
Length of output: 3805
🏁 Script executed:
#!/bin/bash
# Check tsconfig structure and if icons are excluded/included
echo "=== Full tsconfig.json ==="
cat tsconfig.json
echo -e "\n=== Check if separate tsconfigs exist in components ==="
find components -name 'tsconfig*.json' 2>/dev/null
echo -e "\n=== Check build scripts to see if strict checks are enforced ==="
if [ -f package.json ]; then
rg '"(build|type-check|lint)"' package.json -A 2
fiRepository: asyncapi/website
Length of output: 1056
Add TypeScript types to component props to satisfy strict mode.
At Line 4, { className = '' } is untyped. The repository has strict: true enabled in tsconfig.json, which treats untyped destructured parameters as implicit any. This will cause TypeScript errors in the build. Use React.SVGProps<SVGSVGElement> as seen in other icon components like ArrowRightStroke.tsx and BadgeCheckmark.tsx.
Suggested fix
+import type { SVGProps } from 'react';
+
-export default function IconBook({ className = '' }) {
+export default function IconBook({ className = '', ...props }: SVGProps<SVGSVGElement>) {
return (
- <svg className={className} fill='none' viewBox='0 0 24 24' stroke='currentColor' xmlns='http://www.w3.org/2000/svg'>
+ <svg
+ className={className}
+ fill='none'
+ viewBox='0 0 24 24'
+ stroke='currentColor'
+ xmlns='http://www.w3.org/2000/svg'
+ {...props}
+ >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/icons/Book.tsx` around lines 4 - 6, The IconBook component's props
are untyped causing strict-mode errors; update the function signature for
IconBook to accept a typed props parameter (use React.SVGProps<SVGSVGElement> or
an explicit interface that extends it) and apply that type to the destructured
className prop so the component matches other icons like ArrowRightStroke and
BadgeCheckmark and satisfies tsconfig's strict mode.
| <article className='flex h-full flex-col overflow-hidden rounded-lg bg-white shadow-md hover:shadow-lg'> | ||
| <div className='relative h-40 bg-gradient-to-br from-indigo-600 to-purple-600 p-4'> | ||
| <div className='absolute right-4 top-4'> | ||
| <span className='rounded-full bg-white px-3 py-1 text-xs font-medium text-gray-700'> | ||
| {type || 'Online'} | ||
| </span> | ||
| </div> | ||
| <div className='absolute bottom-4 left-4'> | ||
| <span className='text-sm font-semibold text-white'>150 attending</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className='flex flex-1 flex-col justify-between p-6'> | ||
| <div> | ||
| <Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.bodyLg} className='mb-2 text-gray-900'> | ||
| {post.title} | ||
| </Heading> | ||
| <div className='flex items-center text-sm text-gray-600'> | ||
| <span data-testid='Event-span'> | ||
| {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'} | ||
| </span> | ||
| <ArrowRightIcon className='ml-3 w-4' /> | ||
| </div> | ||
| <div className='text-sm text-gray-600'>{postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}</div> | ||
| </div> | ||
| </a> | ||
|
|
||
| <a | ||
| href={post.url} | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| className='mt-4 block w-full rounded-lg bg-indigo-600 py-3 text-center font-semibold text-white transition-colors hover:bg-indigo-700' | ||
| data-testid='EventPostItem-link' | ||
| > | ||
| Join Our Community | ||
| </a> | ||
| </div> |
There was a problem hiding this comment.
Missing dark mode styling.
This component lacks dark mode classes, which is inconsistent with the rest of this dark theme PR. The card background, text colors, and CTA button should have dark variants.
Suggested dark mode additions
- <article className='flex h-full flex-col overflow-hidden rounded-lg bg-white shadow-md hover:shadow-lg'>
+ <article className='flex h-full flex-col overflow-hidden rounded-lg bg-white dark:bg-dark-card shadow-md dark:shadow-lg hover:shadow-lg dark:hover:shadow-xl'>- <Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.bodyLg} className='mb-2 text-gray-900'>
+ <Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.bodyLg} className='mb-2 text-gray-900 dark:text-white'>- <div className='flex items-center text-sm text-gray-600'>
+ <div className='flex items-center text-sm text-gray-600 dark:text-gray-400'>- <div className='text-sm text-gray-600'>{postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}</div>
+ <div className='text-sm text-gray-600 dark:text-gray-400'>{postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}</div>- className='mt-4 block w-full rounded-lg bg-indigo-600 py-3 text-center font-semibold text-white transition-colors hover:bg-indigo-700'
+ className='mt-4 block w-full rounded-lg bg-indigo-600 dark:bg-indigo-500 py-3 text-center font-semibold text-white transition-colors hover:bg-indigo-700 dark:hover:bg-indigo-600'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <article className='flex h-full flex-col overflow-hidden rounded-lg bg-white shadow-md hover:shadow-lg'> | |
| <div className='relative h-40 bg-gradient-to-br from-indigo-600 to-purple-600 p-4'> | |
| <div className='absolute right-4 top-4'> | |
| <span className='rounded-full bg-white px-3 py-1 text-xs font-medium text-gray-700'> | |
| {type || 'Online'} | |
| </span> | |
| </div> | |
| <div className='absolute bottom-4 left-4'> | |
| <span className='text-sm font-semibold text-white'>150 attending</span> | |
| </div> | |
| </div> | |
| <div className='flex flex-1 flex-col justify-between p-6'> | |
| <div> | |
| <Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.bodyLg} className='mb-2 text-gray-900'> | |
| {post.title} | |
| </Heading> | |
| <div className='flex items-center text-sm text-gray-600'> | |
| <span data-testid='Event-span'> | |
| {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'} | |
| </span> | |
| <ArrowRightIcon className='ml-3 w-4' /> | |
| </div> | |
| <div className='text-sm text-gray-600'>{postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}</div> | |
| </div> | |
| </a> | |
| <a | |
| href={post.url} | |
| target='_blank' | |
| rel='noreferrer' | |
| className='mt-4 block w-full rounded-lg bg-indigo-600 py-3 text-center font-semibold text-white transition-colors hover:bg-indigo-700' | |
| data-testid='EventPostItem-link' | |
| > | |
| Join Our Community | |
| </a> | |
| </div> | |
| <article className='flex h-full flex-col overflow-hidden rounded-lg bg-white dark:bg-dark-card shadow-md dark:shadow-lg hover:shadow-lg dark:hover:shadow-xl'> | |
| <div className='relative h-40 bg-gradient-to-br from-indigo-600 to-purple-600 p-4'> | |
| <div className='absolute right-4 top-4'> | |
| <span className='rounded-full bg-white px-3 py-1 text-xs font-medium text-gray-700'> | |
| {type || 'Online'} | |
| </span> | |
| </div> | |
| <div className='absolute bottom-4 left-4'> | |
| <span className='text-sm font-semibold text-white'>150 attending</span> | |
| </div> | |
| </div> | |
| <div className='flex flex-1 flex-col justify-between p-6'> | |
| <div> | |
| <Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.bodyLg} className='mb-2 text-gray-900 dark:text-white'> | |
| {post.title} | |
| </Heading> | |
| <div className='flex items-center text-sm text-gray-600 dark:text-gray-400'> | |
| <span data-testid='Event-span'> | |
| {active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'} | |
| </span> | |
| </div> | |
| <div className='text-sm text-gray-600 dark:text-gray-400'>{postDate.isValid() && moment(postDate).format('h:mm A [UTC]')}</div> | |
| </div> | |
| <a | |
| href={post.url} | |
| target='_blank' | |
| rel='noreferrer' | |
| className='mt-4 block w-full rounded-lg bg-indigo-600 dark:bg-indigo-500 py-3 text-center font-semibold text-white transition-colors hover:bg-indigo-700 dark:hover:bg-indigo-600' | |
| data-testid='EventPostItem-link' | |
| > | |
| Join Our Community | |
| </a> | |
| </div> | |
| </article> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/navigation/EventPostItem.tsx` around lines 48 - 82, Add Tailwind
dark-mode variants to the card and its children in EventPostItem.tsx: change the
root article classes to include dark background and shadow (e.g., add
dark:bg-gray-800 dark:shadow-lg), update the top gradient div to include darker
gradient variants (e.g., dark:from-indigo-700 dark:to-purple-700), make the
badge span use dark:bg-gray-700 and dark:text-gray-200, change Heading text
class (where Heading is rendered with post.title) from text-gray-900 to
text-gray-900 dark:text-gray-100, update body text spans and time from
text-gray-600 to text-gray-600 dark:text-gray-400, and update the CTA anchor
from bg-indigo-600 hover:bg-indigo-700 to include dark:bg-indigo-500
dark:hover:bg-indigo-600 (keep text-white). Ensure all changes reference the
same JSX elements (article, top gradient div, badge span, Heading, text spans
using postDate, and the CTA anchor) so the component matches the rest of the
dark theme.
| <Link | ||
| href={item.link} | ||
| className='text-md font-medium leading-5 text-gray-900 hover:underline' | ||
| className='text-md font-medium leading-5 text-gray-900 dark:text-gray-300 hover:underline' | ||
| target='_blank' | ||
| > |
There was a problem hiding this comment.
Add rel to external new-tab link.
Line 215 uses target='_blank' without rel='noopener noreferrer', which can expose window.opener.
🔒 Proposed fix
<Link
href={item.link}
className='text-md font-medium leading-5 text-gray-900 dark:text-gray-300 hover:underline'
target='_blank'
+ rel='noopener noreferrer'
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Link | |
| href={item.link} | |
| className='text-md font-medium leading-5 text-gray-900 hover:underline' | |
| className='text-md font-medium leading-5 text-gray-900 dark:text-gray-300 hover:underline' | |
| target='_blank' | |
| > | |
| <Link | |
| href={item.link} | |
| className='text-md font-medium leading-5 text-gray-900 dark:text-gray-300 hover:underline' | |
| target='_blank' | |
| rel='noopener noreferrer' | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/casestudies/`[id].tsx around lines 212 - 216, The external link using
the Link component (the element rendering href={item.link} with target='_blank')
should include rel='noopener noreferrer' to prevent window.opener exposure;
update that Link element to add rel="noopener noreferrer" (preserving existing
className and target) so external new-tab links are safe.
| <Button | ||
| text='Join Our Community' | ||
| href='https://asyncapi.slack.com/join/shared_invite/zt-3clk6rmc0-Cujl2fChHYnHDUwFKRlQCw' | ||
| className='w-full sm:w-auto bg-transparent border-2 border-white text-white hover:bg-white/10' |
There was a problem hiding this comment.
Use the canonical Slack invite URL here.
The hard-coded shared_invite/zt-... token will eventually rotate or expire, which turns this CTA into a dead link. Reuse the stable /slack-invite URL or a shared constant instead.
🔗 Suggested fix
- href='https://asyncapi.slack.com/join/shared_invite/zt-3clk6rmc0-Cujl2fChHYnHDUwFKRlQCw'
+ href='https://asyncapi.com/slack-invite'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button | |
| text='Join Our Community' | |
| href='https://asyncapi.slack.com/join/shared_invite/zt-3clk6rmc0-Cujl2fChHYnHDUwFKRlQCw' | |
| className='w-full sm:w-auto bg-transparent border-2 border-white text-white hover:bg-white/10' | |
| <Button | |
| text='Join Our Community' | |
| href='https://asyncapi.com/slack-invite' | |
| className='w-full sm:w-auto bg-transparent border-2 border-white text-white hover:bg-white/10' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/casestudies/index.tsx` around lines 297 - 300, Replace the hard-coded
rotating Slack invite token in the Button component (the href on the Button with
text 'Join Our Community') with the canonical stable invite path (e.g.,
'/slack-invite') or reference a shared constant (e.g., SLACK_INVITE_URL) so the
CTA won't break when tokens rotate; update the Button's href to use that
constant or path and ensure the constant is exported/imported where used.
| </Paragraph> | ||
| <ul className='mt-8 flex flex-wrap justify-center gap-3 lg:justify-start'> | ||
| <li> | ||
| <GoogleCalendarButton href='https://calendar.google.com/calendar/u/3?cid=Y19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t' /> |
There was a problem hiding this comment.
Don’t pin the Calendar CTA to u/3.
This URL targets the fourth signed-in Google account. For many visitors that opens the wrong account selector or misses the intended calendar entirely; use an account-neutral add-calendar URL or shared constant instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/community/events-and-updates.tsx` at line 164, The GoogleCalendarButton
is using a hardcoded account index in its href ('u/3'), which pins the CTA to
the fourth signed-in Google account; update the href passed to
GoogleCalendarButton to an account-neutral add-to-calendar URL (remove the
/u/{n} segment) or reference a shared constant (e.g., GOOGLE_CALENDAR_ADD_URL)
instead, ensuring the cid parameter stays the same so the calendar is correctly
added for any signed-in account.
| <div className='w-full sm:w-auto sm:flex-1 max-w-md'> | ||
| <input | ||
| type='text' | ||
| placeholder='Search members by name or GitHub handle...' | ||
| value={searchTerm} | ||
| onChange={(e) => { | ||
| setSearchTerm(e.target.value); | ||
| setCurrentPage(1); | ||
| }} | ||
| className='w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent' | ||
| /> |
There was a problem hiding this comment.
Add a real label for the search field.
This input relies on placeholder text only, which does not provide a stable accessible name for assistive tech. Please wire a <label> or at least an aria-label to it.
♿ Quick fix
<div className='w-full sm:w-auto sm:flex-1 max-w-md'>
+ <label htmlFor='tsc-member-search' className='sr-only'>
+ Search TSC members
+ </label>
<input
+ id='tsc-member-search'
type='text'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className='w-full sm:w-auto sm:flex-1 max-w-md'> | |
| <input | |
| type='text' | |
| placeholder='Search members by name or GitHub handle...' | |
| value={searchTerm} | |
| onChange={(e) => { | |
| setSearchTerm(e.target.value); | |
| setCurrentPage(1); | |
| }} | |
| className='w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent' | |
| /> | |
| <div className='w-full sm:w-auto sm:flex-1 max-w-md'> | |
| <label htmlFor='tsc-member-search' className='sr-only'> | |
| Search TSC members | |
| </label> | |
| <input | |
| id='tsc-member-search' | |
| type='text' | |
| placeholder='Search members by name or GitHub handle...' | |
| value={searchTerm} | |
| onChange={(e) => { | |
| setSearchTerm(e.target.value); | |
| setCurrentPage(1); | |
| }} | |
| className='w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-card text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent' | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/community/tsc.tsx` around lines 208 - 218, The search input currently
uses only a placeholder and lacks an accessible name; update the input in the
component that uses searchTerm and setSearchTerm to provide a stable accessible
label by either adding a visible <label> tied to the input's id or adding an
aria-label (e.g., aria-label="Search members by name or GitHub handle"); ensure
the label text matches the placeholder intent and that the input retains its
existing props (value={searchTerm}, onChange calling setSearchTerm and
setCurrentPage) so behavior is unchanged.
| "body_boldtext": "Industriestandard", | ||
| "body_posttext": " zur Definition asynchroner APIs.", | ||
| "docs_btn": "Lesen Sie die Dokumente", | ||
| "community_btn": "sluit je aan bij onze gemeenschap", |
There was a problem hiding this comment.
German locale includes Dutch text in new keys.
Line [9] and Lines [78-79] are Dutch, which breaks DE localization consistency.
Suggested correction (German)
- "community_btn": "sluit je aan bij onze gemeenschap",
+ "community_btn": "Treten Sie unserer Gemeinschaft bei",
@@
- "description":"AsyncAPI organiseert wekelijks verschillende bijeenkomsten. Deze richten zich op verschillende onderwerpen, soms puur technisch en soms over community-building. Kies er een en doe mee!",
- "meetingTitle":"Lees meer over onze bijeenkomsten."
+ "description":"AsyncAPI organisiert wöchentlich verschiedene Treffen. Sie behandeln unterschiedliche Themen, manchmal rein technisch und manchmal rund um den Aufbau der Community. Wählen Sie eines aus und machen Sie mit!",
+ "meetingTitle":"Erfahren Sie mehr über unsere Treffen."Also applies to: 78-79
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@public/locales/de/landing-page.json` at line 9, Replace the Dutch strings in
the German locale for the key "community_btn" (currently "sluit je aan bij onze
gemeenschap") and the other newly added keys near the end of the file that
contain Dutch text with their proper German translations; update the values to
German equivalents (e.g., "Tritt unserer Community bei" for "community_btn"),
ensure quotation and JSON syntax remain valid, and run the locale/translation
validator or tests to confirm DE localization consistency.
Visual UI Review
One more interesting behvior is when a hard refresh (Ctrl + F5) is done while the new link the link is changed, it renders properly. Attached a video for better demnostration.
Disclaimer: I'm not a very design-intensive person 😅 |
3. Need to check this again. I already had fixed it earlier though.
|












Summary by CodeRabbit
New Features
Bug Fixes
Style