Skip to content

Conversation

thesmithdao
Copy link
Collaborator

@thesmithdao thesmithdao commented Sep 30, 2025

Support Page Revamp

Summary

Adds a grouped-by-tag support landing (preview cards), tag-filtered paginated pages for SEO, server-rendered article pages preserving tag context, client search, and robust markdown rendering.

Key changes

  • /support: grouped tag overview with preview cards and “View all”.
  • /support?tag=<Tag>: tag-filtered, paginated list (SEO-friendly).
  • /support/<slug>?tag=<Tag>: article page that preserves source tag.
  • Client: search that respects active tag; tag pills use type="button"; tag-discovery preserves master list.
  • Markdown props sanitized; article fetch uses cache: 'no-store'.

Quick tests

  • Open /support → grouped sections with preview cards + “View all”.
  • Click tag pill or “View all” → /support?tag=<Tag> (pagination if > pageSize).
  • Open article → long form content at /support/<slug>?tag=<Tag>; Back returns to filtered list.
  • Search filters client-side and respects active tag; tag pills do not cause layout shift.

Acceptance

  • Grouped landing shows previews and “View all”.
  • Tag pages are paginated and SEO-friendly.
  • Articles are server-rendered and show source tag; Back works.
  • Search + tag filtering behave correctly; tag pills are buttons.

Before merge

null

After merge

Screenshot 2025-10-11 at 12 20 44

Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

📝 Walkthrough

Walkthrough

Reworks support article listing to be tag-aware and searchable, extends Strapi types and cached params, updates fetch hook to accept tag/search filters, converts article detail page to an async server component with a small client companion, centralizes Markdown rendering, and exposes a per-request nonce via meta tag.

Changes

Cohort / File(s) Summary of changes
Listing & list behavior
app/[lang]/(resources)/_components/SupportArticleList.tsx
Replaced static grid with tag-aware UI: reads activeTag from URL/prop, supports client-side searchQuery, shows either paginated tag-specific list or grouped-by-tag previews, builds groupedTags/groupedArticles, integrates ArticleCard, SupportTags, SearchBar, and conditional pagination and loading/empty states.
Tag UI
app/[lang]/(resources)/support/_components/SupportTags.tsx
New pill-style tag selector component rendering "All" and per-tag buttons, supports active highlighting and `onClick(tag
Search input
app/[lang]/_components/SearchBar.tsx
Added optional inputClassName prop and used it to compute input classes; minor formatting tweaks.
Detail page (server) + client companion
app/[lang]/(resources)/support/[slug]/page.tsx, app/[lang]/(resources)/support/_components/SupportArticleClient.tsx
Converted detail page to an async server component that validates slug, fetches article from Strapi (optionally with token), returns notFound if absent, emits SEO Script and renders Banner/SupportArticleContent; adds Suspense-wrapped SupportArticleClient for back navigation and tag context.
Markdown rendering for content
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx
Centralized components map for ReactMarkdown (headers, code, tables, images, lists, links, paragraphs), added sanitize helper, Next/Image renderer with responsive settings, table scrolling, code language badge, and preserved remark/rehype plugins.
Fetch hook & caching/context types
app/[lang]/_hooks/useFetchSupportArticles.ts, app/[lang]/_contexts/CachedArticlesContext.tsx
useFetchSupportArticles now accepts tag? and search?, applies corresponding Strapi filters and encodes params; cached params type (TCachedParams) extended with type?, tag?, search? and defaults/equality checks updated.
Strapi types
app/[lang]/_components/strapi/types.ts
TSupportArticle now includes optional tags?: string[] and type?: string[]; widespread punctuation/formatting normalization across type declarations.
Layout nonce exposure
app/layout.tsx
Adds meta tag x-nonce in head to expose per-request nonce to client code; nonce continues to be applied to Script.
TypeScript config
tsconfig.json
Adds "scripts" to exclude.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant L as SupportArticleList (client)
  participant T as SupportTags
  participant S as SearchBar
  participant H as useFetchSupportArticles
  participant API as Strapi API

  U->>L: Load page / interact
  L->>T: render tags (active or grouped)
  L->>S: render search input
  T-->>L: onClick(tag|null)
  S-->>L: onChange(searchQuery)
  L->>H: fetch({page, pageSize, tag?, search?})
  H->>API: GET /articles?filters[tag|search]...
  API-->>H: Articles + meta
  H-->>L: Data
  alt active tag present
    L-->>U: Paginated grid of filtered articles
  else no active tag
    L-->>U: Grouped-by-tag sections + per-tag "View all"
  end
Loading
sequenceDiagram
  autonumber
  participant Browser as U
  participant NextServer as S
  participant Page as support/[slug]/page.tsx
  participant API as Strapi
  participant ClientComp as SupportArticleClient

  U->>S: Request /support/[slug]
  S->>Page: invoke async server component
  Page->>API: GET /articles?filters[slug]=...&populate=...
  alt article found
    API-->>Page: article data
    Page-->>S: render HTML (SEO Script, Banner, SupportArticleContent, <ClientComp/>)
    S-->>U: HTML
    U->>ClientComp: hydrate client component
    ClientComp-->>U: back button + optional tag display
  else missing/error
    Page-->>S: notFound()
    S-->>U: 404
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • 0xApotheosis

Poem

A rabbit taps keys with a gentle hop,
Tags bloom like clover in a tidy crop.
Search sniffs corners where hidden stories hide,
Server fetches bring the meadow wide.
Back button ready — off we dart with joy and art. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “feat: support page revamp” succinctly captures the main change of the pull request, namely the overhaul of the support pages, and aligns with the conventional commit style. It is concise, specific to the core functionality being introduced, and free of unnecessary noise or vague wording. Teammates scanning the commit history will immediately understand that this PR implements a support page redesign.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)

9-28: Update JSDoc to document new parameters.

The JSDoc comment should be updated to include the newly added tag and search parameters.

Apply this diff:

  * @param {string} options.slug - Optional slug to filter by specific article
+ * @param {string} options.tag - Optional tag to filter articles by
+ * @param {string} options.search - Optional search query to filter by title or summary
  * @param {boolean} options.populateContent - Whether to include full content (default: false)
🧹 Nitpick comments (12)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)

85-94: Consider extracting URL construction to a helper function.

The fetch URL construction on line 88 is extremely long (400+ characters) and hard to read/maintain. Consider extracting this to a dedicated helper function that builds the URL from parameters.

Example refactor:

function buildSupportArticlesUrl({
	sort,
	page,
	pageSize,
	populateContent,
	slug,
	encodedTag,
	encodedSearch
}: {
	sort: string
	page: number
	pageSize: number
	populateContent: boolean
	slug?: string
	encodedTag?: string
	encodedSearch?: string
}): string {
	const baseUrl = `${process.env.NEXT_PUBLIC_STRAPI_URL}/api/support-articles`
	const params = new URLSearchParams({
		'populate[0]': 'featuredImg',
		'fields[0]': 'slug',
		'fields[1]': 'summary',
		'fields[2]': 'title',
		'fields[3]': 'publishedAt',
		'fields[4]': 'tags',
		'sort[0]': `publishedAt:${sort}`,
		'pagination[page]': page.toString(),
		'pagination[pageSize]': pageSize.toString(),
		'pagination[withCount]': 'true'
	})
	
	if (populateContent) params.append('fields[5]', 'content')
	if (slug) params.append('filters[slug][$eq]', slug)
	if (encodedTag) params.append('filters[tags][$contains]', encodedTag)
	if (encodedSearch) {
		params.append('filters[$or][0][title][$containsi]', encodedSearch)
		params.append('filters[$or][1][summary][$containsi]', encodedSearch)
	}
	
	return `${baseUrl}?${params.toString()}`
}

Then use: const res = await fetch(buildSupportArticlesUrl({...}), {...})

app/[lang]/(resources)/support/_components/SupportTags.tsx (2)

20-27: Simplify the active state check.

The condition active === null || active === undefined is redundant. Since active is typed as string | null | undefined, you can simplify this check.

Apply this diff:

-	className={`rounded-full border px-4 py-2 text-sm ${
-		active === null || active === undefined ? 'bg-white/10' : 'bg-transparent'
-	}`}>
+	className={`rounded-full border px-4 py-2 text-sm ${
+		!active ? 'bg-white/10' : 'bg-transparent'
+	}`}>

17-39: Consider accessibility enhancements for the tag filter.

While the component is functional, consider these accessibility improvements:

  1. Add aria-label to the "All" button (e.g., "Show all articles")
  2. Add role="group" and aria-label="Filter by tag" to the container
  3. Add aria-pressed to buttons to indicate toggle state

Example enhancement:

-	<div className={`${className ?? ''} mb-10`}>
+	<div className={`${className ?? ''} mb-10`} role="group" aria-label="Filter by tag">
 		<div className={'flex flex-wrap gap-3 justify-center'}>
 			<button
 				type={'button'}
 				onClick={() => onClick(null)}
+				aria-label="Show all articles"
+				aria-pressed={!active}
 				className={`rounded-full border px-4 py-2 text-sm ${
 					active === null || active === undefined ? 'bg-white/10' : 'bg-transparent'
 				}`}>
 				{'All'}
 			</button>
 			{tags.map(t => (
 				<button
 					type={'button'}
 					key={t}
 					onClick={() => onClick(t)}
+					aria-pressed={active === t}
 					className={`rounded-full border px-4 py-2 text-sm ${active === t ? 'bg-white/10' : 'bg-transparent'}`}>
 					{t}
 				</button>
 			))}
 		</div>
 	</div>
app/[lang]/(resources)/_components/SupportArticleList.tsx (7)

58-70: Optimization: fetchPageSize is always equal to pageSize.

Lines 58-59 assign fetchPageSize = pageSize unconditionally. The variable fetchPageSize adds no value and can be replaced with pageSize directly.

Apply this diff to simplify:

-	// server-side filtering: always use the configured pageSize; grouped view will show previews and
-	// provide "View all" links to the paginated tag view instead of fetching the entire dataset.
-	const fetchPage = activeTag ? page : 1
-	const fetchPageSize = pageSize
-
 	const {articles, pagination, isLoading} = useFetchSupportArticles({
-		page: fetchPage,
-		pageSize: fetchPageSize,
+		page: activeTag ? page : 1,
+		pageSize,
 		sort,
 		populateContent,
 		cacheArticles,
 		tag: activeTag,
 		search: searchQuery,
 		skip: false
 	})

76-76: Remove unused variable.

filteredArticles is assigned the value of articles but doesn't perform any filtering. Since the server already returns filtered results, directly use articles throughout the component.

Apply this diff to remove the unused variable:

-	// Server returns results already filtered by tag and search; render `articles` directly
-	const filteredArticles = articles
-
 	// When in the grouped view (no active tag), prepare tags and grouped lists
 	const groupedTags = useMemo(() => {
-		if (!filteredArticles) {
+		if (!articles) {
 			return []
 		}
-		return Array.from(new Set(filteredArticles.flatMap(a => a.tags ?? []))).sort()
-	}, [filteredArticles])
+		return Array.from(new Set(articles.flatMap(a => a.tags ?? []))).sort()
+	}, [articles])

 	const groupedArticles = useMemo(() => {
 		const map: Record<string, TSupportArticle[]> = {}
 		for (const t of groupedTags) {
-			map[t] = (filteredArticles || []).filter(a => (a.tags ?? []).includes(t))
+			map[t] = (articles || []).filter(a => (a.tags ?? []).includes(t))
 		}
 		return map
-	}, [filteredArticles, groupedTags])
+	}, [articles, groupedTags])

And update references in the render section:

-		{!filteredArticles || filteredArticles.length === 0 ? (
+		{!articles || articles.length === 0 ? (
 			<p
 				className={'my-20 text-center text-2xl text-gray-400'}
 				role={'status'}>
@@ -172,7 +172,7 @@
 						</div>
 						<div className={cl('grid gap-6 md:grid-cols-2 lg:grid-cols-3', gridClassName)}>
-							{filteredArticles.map((article: TSupportArticle) => (
+							{articles.map((article: TSupportArticle) => (
 								<ArticleCard

79-84: Handle undefined articles gracefully.

The null-check on Line 80 only guards against undefined/null but doesn't account for an empty array. While not a bug (empty array returns empty groupedTags), the logic would be clearer by checking array length.

Apply this diff to make the intent clearer:

 	const groupedTags = useMemo(() => {
-		if (!filteredArticles) {
+		if (!articles || articles.length === 0) {
 			return []
 		}
-		return Array.from(new Set(filteredArticles.flatMap(a => a.tags ?? []))).sort()
-	}, [filteredArticles])
+		return Array.from(new Set(articles.flatMap(a => a.tags ?? []))).sort()
+	}, [articles])

130-157: Review the responsive width constraint.

Lines 131-156 wrap SupportTags and SearchBar in a w-1/2 container. On smaller screens this may be too narrow. Consider using responsive width classes like w-full lg:w-1/2 for better mobile UX.

Apply this diff to improve mobile responsiveness:

 			<div className={'flex w-full justify-center'}>
-				<div className={'w-1/2'}>
+				<div className={'w-full px-4 lg:w-1/2 lg:px-0'}>
 					<SupportTags

138-148: Simplify tag state derivation.

Line 138 uses urlTag ?? tag ?? null, but activeTag (Line 54) already computes urlTag ?? tag. Reuse activeTag for consistency.

Apply this diff:

 					<SupportTags
 						tags={
 							activeTag
 								? Array.from(new Set((articles || []).flatMap(a => a.tags ?? [])))
 								: groupedTags
 						}
-						active={urlTag ?? tag ?? null}
+						active={activeTag ?? null}
 						onClick={(t: string | null) => {

160-165: Enhance empty state accessibility.

The empty state paragraph uses role={'status'} which is appropriate for dynamic content updates. However, consider adding aria-live="polite" to ensure screen readers announce changes when search/filter results change.

Apply this diff:

 			<p
-				className={'my-20 text-center text-2xl text-gray-400'}
-				role={'status'}>
+				className={'my-20 text-center text-2xl text-gray-400'}
+				role={'status'}
+				aria-live={'polite'}>
 				{emptyMessage}
 			</p>

46-48: Language parameter may be undefined for root routes

There are app/layout.tsx and app/page.tsx at the root (outside [lang]), so lang can legitimately be undefined and defaulting to '' is expected. If you intend to require lang across all routes, consider moving these files under app/[lang] or adding a redirect from / to a default language.

app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)

22-22: Enhance sourceTag display with semantic HTML.

The source tag paragraph could benefit from a more descriptive label or semantic tag rendering for clarity.

Consider applying this diff for better UX:

-			{sourceTag && <p className={'mb-2 text-sm text-gray-400'}>{sourceTag}</p>}
+			{sourceTag && (
+				<p className={'mb-2 text-sm text-gray-400'}>
+					<span className={'font-semibold'}>{'Category: '}</span>
+					{sourceTag}
+				</p>
+			)}
app/[lang]/(resources)/support/[slug]/page.tsx (1)

47-48: Consider making baseUrl configurable with fallback.

The baseUrl defaults to 'https://shapeshift.com' if NEXT_PUBLIC_SITE_URL is missing. This may cause incorrect URLs in non-production environments.

Apply this diff to make the fallback more explicit:

 	// Generate structured data for the support article
-	const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://shapeshift.com'
+	const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? (process.env.NODE_ENV === 'production' ? 'https://shapeshift.com' : 'http://localhost:3000')
 	const articleSchema = generateSupportArticleSchema(article, baseUrl)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 29665de and 8f82e27.

📒 Files selected for processing (8)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (4 hunks)
  • app/[lang]/(resources)/support/[slug]/page.tsx (2 hunks)
  • app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1 hunks)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx (1 hunks)
  • app/[lang]/_components/SearchBar.tsx (2 hunks)
  • app/[lang]/_components/strapi/types.ts (1 hunks)
  • app/[lang]/_contexts/CachedArticlesContext.tsx (5 hunks)
  • app/[lang]/_hooks/useFetchSupportArticles.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (2)
app/[lang]/_components/strapi/types.ts (3)
  • TSupportArticle (303-317)
  • TPagination (125-130)
  • TArticleListResponse (319-324)
app/[lang]/_contexts/CachedArticlesContext.tsx (1)
  • useCachedArticles (114-121)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-40)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
app/[lang]/_contexts/CachedArticlesContext.tsx (1)
app/[lang]/_components/strapi/types.ts (1)
  • TArticleListResponse (319-324)
app/[lang]/(resources)/support/[slug]/page.tsx (2)
app/[lang]/_utils/schema.ts (1)
  • generateSupportArticleSchema (112-138)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)
  • SupportArticleClient (9-25)
🔇 Additional comments (15)
app/[lang]/_components/strapi/types.ts (1)

303-317: LGTM! Well-documented optional fields for tag filtering.

The addition of optional tags and type fields to TSupportArticle aligns well with the tag-based filtering introduced across the PR. The inline comment provides helpful context about the recent addition.

app/[lang]/_components/SearchBar.tsx (2)

3-7: LGTM! Flexible className prop added.

The optional inputClassName prop enhances reusability without breaking existing usage.


18-18: Potential className conflict with width utilities.

When inputClassName contains width classes (e.g., w-full), both the provided width and the default w-1/2 will be in the className string, leading to specificity conflicts. Consider either:

  1. Replacing the entire className when inputClassName is provided, or
  2. Documenting that inputClassName should include width if customization is needed.

Apply this diff for approach 1 (full replacement):

-	className={`${inputClassName ?? 'w-1/2'} rounded-2xl border-none bg-white/5 px-5 py-6 text-white outline-none focus:border-none focus:outline-none focus:ring-1 focus:ring-white/50`}
+	className={inputClassName ?? 'w-1/2 rounded-2xl border-none bg-white/5 px-5 py-6 text-white outline-none focus:border-none focus:outline-none focus:ring-1 focus:ring-white/50'}

Or for approach 2, add a JSDoc comment:

+/**
+ * @param inputClassName - Custom className for the input (should include width if specified)
+ */
 export function SearchBar({searchQuery, setSearchQueryAction, inputClassName}: TSearchProps): JSX.Element {

Likely an incorrect or invalid review comment.

app/[lang]/_hooks/useFetchSupportArticles.ts (2)

67-81: LGTM! Cache invalidation correctly includes tag and search.

The cache checking logic properly validates the new tag and search parameters alongside existing filters.


116-141: LGTM! Effect dependencies are comprehensive.

All new parameters (tag, search) and their cached counterparts are correctly included in the dependency array.

app/[lang]/(resources)/support/_components/SupportTags.tsx (1)

3-16: LGTM! Clean component signature with appropriate guards.

The props are well-typed and the early return for empty tags prevents unnecessary rendering.

app/[lang]/_contexts/CachedArticlesContext.tsx (2)

21-31: LGTM! Well-structured type extension for filtering.

The addition of optional type, tag, and search fields to TCachedParams is clean and maintains backward compatibility. The comment provides helpful context.


55-65: LGTM! Consistent initialization across context and state.

The new filter fields are correctly initialized to undefined in both the context default and state initialization, ensuring consistency.

Also applies to: 85-94

app/[lang]/(resources)/_components/SupportArticleList.tsx (3)

94-113: LGTM: ArticleCard implementation is clean and accessible.

The component correctly constructs tag-aware URLs, uses semantic HTML, and provides good visual hierarchy.


184-207: LGTM: Grouped view implementation is clean and user-friendly.

The grouped-by-tag sections with "View all" links provide good UX and clear navigation. The .slice(0, 3) preview is a reasonable default.


211-242: LGTM: Pagination logic correctly conditional on activeTag.

Pagination only renders when activeTag is present and there are multiple pages, which aligns with the hybrid view design.

app/[lang]/(resources)/support/[slug]/page.tsx (4)

15-16: LGTM: Server component conversion improves SEO and performance.

Converting this page to an async server component with server-side data fetching is an excellent architectural choice for a content-driven page.


30-44: LGTM: Fetch logic with appropriate error handling.

The server-side fetch correctly handles missing tokens, response errors, and missing article data with appropriate calls to notFound().


61-69: LGTM: Suspense integration is clean and provides good loading UX.

The Suspense boundary with a loading skeleton for the Back button provides a smooth user experience while SupportArticleClient hydrates.


71-75: Remove unnecessary null-check for article.content The Strapi types declare content: string (non-optional), so adding a guard isn’t needed.

@thesmithdao thesmithdao marked this pull request as draft September 30, 2025 20:03
@thesmithdao thesmithdao force-pushed the support-lp branch 3 times, most recently from 9381fb0 to b22e16b Compare September 30, 2025 22:14
@thesmithdao thesmithdao marked this pull request as ready for review September 30, 2025 22:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)

16-23: Accessibility improvements successfully implemented.

The previous review comments have been addressed—type="button", aria-label, and responsive hidden lg:flex classes are all in place.

🧹 Nitpick comments (2)
app/[lang]/_hooks/useFetchSupportArticles.ts (2)

88-88: Consider using field names without array indices for maintainability.

The hardcoded field array indices (fields[0]=slug, fields[1]=summary, etc.) are brittle—if the Strapi schema or field order changes, this breaks. Strapi typically allows fields=slug,summary,title,publishedAt,tags syntax without indices.

Apply this approach for more maintainable field selection:

-`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/support-articles?populate[0]=featuredImg&fields[0]=slug&fields[1]=summary&fields[2]=title&fields[3]=publishedAt&fields[4]=tags&sort[0]=publishedAt:${sort}&pagination[page]=${page}&pagination[pageSize]=${pageSize}&pagination[withCount]=true${populateContent ? '&fields[5]=content' : ''}${slug ? `&filters[slug][$eq]=${slug}` : ''}${encodedTag ? `&filters[tags][$contains]=${encodedTag}` : ''}${encodedSearch ? `&filters[$or][0][title][$containsi]=${encodedSearch}&filters[$or][1][summary][$containsi]=${encodedSearch}` : ''}`
+`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/support-articles?populate=featuredImg&fields=slug,summary,title,publishedAt,tags${populateContent ? ',content' : ''}&sort=publishedAt:${sort}&pagination[page]=${page}&pagination[pageSize]=${pageSize}&pagination[withCount]=true${slug ? `&filters[slug][$eq]=${slug}` : ''}${encodedTag ? `&filters[tags][$contains]=${encodedTag}` : ''}${encodedSearch ? `&filters[$or][0][title][$containsi]=${encodedSearch}&filters[$or][1][summary][$containsi]=${encodedSearch}` : ''}`

126-130: Optional: Simplify pagination dependencies.

Lines 127-130 list individual pagination properties, but line 126 already references cachedResponse.meta.pagination. The granular properties are redundant. Consider removing lines 127-130 to reduce verbosity without changing behavior.

 		cachedResponse.data,
 		cachedResponse.meta.pagination,
-		cachedResponse.meta.pagination.page,
-		cachedResponse.meta.pagination.pageCount,
-		cachedResponse.meta.pagination.pageSize,
-		cachedResponse.meta.pagination.total,
 		page,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f82e27 and b22e16b.

📒 Files selected for processing (11)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (4 hunks)
  • app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2 hunks)
  • app/[lang]/(resources)/support/[slug]/page.tsx (2 hunks)
  • app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1 hunks)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx (1 hunks)
  • app/[lang]/_components/SearchBar.tsx (2 hunks)
  • app/[lang]/_components/strapi/types.ts (1 hunks)
  • app/[lang]/_contexts/CachedArticlesContext.tsx (5 hunks)
  • app/[lang]/_hooks/useFetchSupportArticles.ts (4 hunks)
  • app/layout.tsx (3 hunks)
  • tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx
  • app/[lang]/_contexts/CachedArticlesContext.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
app/[lang]/(resources)/support/[slug]/page.tsx (3)
app/[lang]/_utils/schema.ts (1)
  • generateSupportArticleSchema (112-138)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)
  • SupportArticleClient (9-27)
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (1)
  • SupportArticleContent (16-234)
app/layout.tsx (1)
next.config.ts (1)
  • headers (13-53)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-40)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
app/[lang]/_hooks/useFetchSupportArticles.ts (2)
app/[lang]/_components/strapi/types.ts (3)
  • TSupportArticle (303-317)
  • TPagination (125-130)
  • TArticleListResponse (319-324)
app/[lang]/_contexts/CachedArticlesContext.tsx (1)
  • useCachedArticles (114-121)
🔇 Additional comments (5)
app/[lang]/_components/SearchBar.tsx (1)

6-18: LGTM! Clean implementation of customizable input width.

The optional inputClassName prop follows good patterns: properly typed, uses nullish coalescing for the default, and integrates cleanly into the existing component.

app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)

9-27: LGTM! Clean client component for navigation and tag context.

The component correctly reads query parameters, provides accessible back navigation, and conditionally displays the source tag. Implementation is straightforward and follows Next.js App Router patterns.

app/[lang]/_hooks/useFetchSupportArticles.ts (3)

85-88: LGTM! Proper URL encoding prevents injection.

The use of encodeURIComponent for both tag and search correctly prevents URL injection attacks. The Strapi filter syntax for tag contains and search OR conditions looks correct.


116-141: Dependency array is complete.

All new parameters (tag, search) are correctly included in both their primitive forms and as cachedParams properties. The array correctly tracks all relevant dependencies.


73-75: Resolved: TCachedParams includes tag and search
Confirmed tag? and search? are defined on TCachedParams in CachedArticlesContext.tsx, so the cache equality check is valid.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)

86-92: Optimize groupedArticles computation.

The current implementation filters filteredArticles for each tag (O(tags × articles)). For better performance, consider grouping articles in a single pass:

 const groupedArticles = useMemo(() => {
   const map: Record<string, TSupportArticle[]> = {}
-  for (const t of groupedTags) {
-    map[t] = (filteredArticles || []).filter(a => (a.tags ?? []).includes(t))
+  for (const article of filteredArticles || []) {
+    for (const t of article.tags ?? []) {
+      if (!map[t]) map[t] = []
+      map[t].push(article)
+    }
   }
   return map
-}, [filteredArticles, groupedTags])
+}, [filteredArticles])

94-113: Consider hoisting ArticleCard outside the component.

Defining ArticleCard inside the parent component causes it to be recreated on every render. For a minor performance optimization, consider hoisting it outside as a standalone component or memoizing it with useCallback. This is optional and unlikely to cause noticeable issues given the current usage.


132-155: Past review comment addressed; consider memoizing tag derivation.

The tag pill survival logic (lines 135-140) correctly ensures that the active tag remains in the list even when filtered results are empty, addressing the previous review concern. For a minor performance improvement, consider extracting the IIFE into a useMemo:

+const displayedTags = useMemo(() => {
+  if (!activeTag) return groupedTags
+  const derived = Array.from(new Set((articles || []).flatMap(a => a.tags ?? [])))
+  return derived.length > 0 ? derived : [activeTag]
+}, [activeTag, articles, groupedTags])
+
 <SupportTags
-  tags={
-    activeTag
-      ? (() => {
-          const derived = Array.from(
-            new Set((articles || []).flatMap(a => a.tags ?? []))
-          )
-          return derived.length > 0 ? derived : [activeTag]
-        })()
-      : groupedTags
-  }
+  tags={displayedTags}
   active={activeTag}
   onClick={(t: string | null) => {

215-247: Consider syncing page state with URL query params.

Pagination state is kept in local state (page) but not reflected in the URL. If a user navigates to page 3 and refreshes, they'll be reset to page 1. For better UX and shareable URLs, consider syncing the page state with a ?page= query parameter.

Example:

// Read page from URL on mount
const urlPage = searchParams?.get('page')
const [page, setPage] = useState(urlPage ? parseInt(urlPage, 10) : initialPage)

// Update URL when page changes
function handlePageChange({selected}: {selected: number}) {
  const newPage = selected + 1
  setPage(newPage)
  const params = new URLSearchParams(Array.from(searchParams || []))
  params.set('page', newPage.toString())
  router.push(`/${lang}/support?${params.toString()}`)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b22e16b and eb024d4.

📒 Files selected for processing (11)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (4 hunks)
  • app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2 hunks)
  • app/[lang]/(resources)/support/[slug]/page.tsx (2 hunks)
  • app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1 hunks)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx (1 hunks)
  • app/[lang]/_components/SearchBar.tsx (2 hunks)
  • app/[lang]/_components/strapi/types.ts (1 hunks)
  • app/[lang]/_contexts/CachedArticlesContext.tsx (5 hunks)
  • app/[lang]/_hooks/useFetchSupportArticles.ts (4 hunks)
  • app/layout.tsx (3 hunks)
  • tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/[lang]/_components/SearchBar.tsx
  • app/layout.tsx
  • app/[lang]/_contexts/CachedArticlesContext.tsx
  • app/[lang]/(resources)/support/_components/SupportTags.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
app/[lang]/(resources)/support/[slug]/page.tsx (3)
app/[lang]/_utils/schema.ts (1)
  • generateSupportArticleSchema (112-138)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)
  • SupportArticleClient (9-27)
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (1)
  • SupportArticleContent (16-234)
app/[lang]/_hooks/useFetchSupportArticles.ts (2)
app/[lang]/_components/strapi/types.ts (3)
  • TSupportArticle (303-317)
  • TPagination (125-130)
  • TArticleListResponse (319-324)
app/[lang]/_contexts/CachedArticlesContext.tsx (1)
  • useCachedArticles (114-121)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-40)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
🔇 Additional comments (11)
tsconfig.json (1)

36-36: Confirm type-check coverage for scripts

Excluding the scripts directory means any TypeScript there will no longer be validated by this config. If we still rely on TS scripts (e.g., build/maintenance tooling), we should either keep them included or give them their own tsconfig. Can you confirm we have coverage elsewhere before merging this?

app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)

9-27: LGTM! Client component properly handles navigation and tag display.

The component correctly uses client-side hooks (useRouter, useSearchParams) to manage back navigation and conditionally display the source tag. The Back button accessibility improvements (type, aria-label, responsive hiding) have been properly addressed per previous review feedback.

app/[lang]/_components/strapi/types.ts (1)

314-316: LGTM! Type extensions support tag-based filtering.

The addition of optional tags and type fields to TSupportArticle aligns with the broader tag-aware filtering and grouping features introduced in this PR. The optional nature ensures backward compatibility with existing data.

app/[lang]/_hooks/useFetchSupportArticles.ts (3)

29-54: LGTM! Hook parameters correctly extended for tag and search filtering.

The addition of tag and search parameters properly extends the hook's capabilities to support the tag-aware filtering and client-side search features described in the PR objectives.


85-88: LGTM! URL construction and filtering logic are correct.

The Strapi filter syntax is properly formed:

  • filters[tags][$contains] for tag matching
  • filters[$or][0][title][$containsi] and filters[$or][1][summary][$containsi] for case-insensitive search across title and summary

The conditional field indexing for content when populateContent is true is also correct.


61-81: LGTM! Cache checks correctly include new filter parameters.

The cache equality logic properly accounts for tag and search parameters, ensuring cached results are only used when all filtering conditions match.

app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2)

84-110: LGTM! Image renderer correctly fixed to prevent prop override.

The destructuring pattern {src, alt, ...rest} and spreading only rest ensures the sanitized srcString and altText are preserved and not overwritten by the original props. The fixed-height container and lazy loading also help prevent cumulative layout shift (CLS) as intended.

This resolves the critical issue flagged in the previous review.


17-149: LGTM! Centralized component mapping improves maintainability.

The refactor to a single components object passed to ReactMarkdown enhances code organization and makes custom rendering logic easier to maintain. The styling and semantic choices for headers, code blocks, tables, and other elements are appropriate.

app/[lang]/(resources)/support/[slug]/page.tsx (2)

15-47: LGTM! Server-side data fetching and validation correctly implemented.

The conversion to an async server component with proper slug validation (regex check), environment variable error handling (throws descriptive error), and server-side Strapi fetch is well executed. The conditional Authorization header and appropriate use of notFound() for missing articles align with Next.js best practices.

All previously flagged issues (slug validation, config error handling) have been properly addressed.


62-70: LGTM! Suspense boundary correctly isolates client component.

Wrapping SupportArticleClient in a Suspense boundary with a skeleton fallback is a best practice for server components that include client-side interactivity. The skeleton matches the visual layout of the Back button, providing a smooth loading experience.

app/[lang]/(resources)/_components/SupportArticleList.tsx (1)

56-60: Verify grouped view article coverage. When no tag is active, the hook fetches only pageSize (default 12) items then groups and slices to 3 per tag—if there are more distinct tags or uneven distribution, some tags or preview slots will be omitted. Confirm this matches product requirements or adjust the fetch size or grouping strategy. [app/[lang]/(resources)/_components/SupportArticleList.tsx:56-60]

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/[lang]/(resources)/_components/SupportArticleList.tsx (2)

130-162: Consider memoizing the derived tag list.

The inline IIFE (lines 135-140) runs on every render. While the past review comment has been addressed functionally (empty results now preserve the active tag), memoizing this computation would improve performance.

+	const displayedTags = useMemo(() => {
+		if (!activeTag) return groupedTags
+		const derived = Array.from(new Set((articles || []).flatMap(a => a.tags ?? [])))
+		return derived.length > 0 ? derived : [activeTag]
+	}, [activeTag, articles, groupedTags])
+
 	// ... later in JSX ...
 	<SupportTags
-		tags={
-			activeTag
-				? (() => {
-						const derived = Array.from(
-							new Set((articles || []).flatMap(a => a.tags ?? []))
-						)
-						return derived.length > 0 ? derived : [activeTag]
-					})()
-				: groupedTags
-		}
+		tags={displayedTags}
 		active={activeTag}

215-247: Consider adding forcePage prop for controlled pagination.

The pagination correctly renders only for tag-filtered views. However, without forcePage, the component maintains its own internal state for the active page, which could desynchronize from your page state if the user navigates via other means (e.g., browser back button or tag changes).

 <ReactPaginate
+	forcePage={page - 1}
 	pageCount={pagination?.pageCount ?? 1}
 	pageRangeDisplayed={5}
 	marginPagesDisplayed={2}
 	onPageChange={({selected}) => setPage(selected + 1)}

This ensures the visual active page always matches your component state.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eb024d4 and 5fd05b9.

📒 Files selected for processing (1)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-40)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
🔇 Additional comments (12)
app/[lang]/(resources)/_components/SupportArticleList.tsx (12)

1-20: LGTM! Imports are well-organized.

All dependencies are properly imported and organized by source (external, internal, local, types).


22-31: LGTM! Type definition is complete.

The props interface correctly includes all configuration options, including the new tag prop for URL-driven filtering.


33-54: LGTM! State management is well-structured.

The component correctly initializes state, derives activeTag from URL parameters (with fallback to prop), and sets up client-side search state.


56-73: LGTM! Data fetching logic is correct.

The conditional fetching strategy correctly handles both grouped (no tag) and paginated (with tag) views. The page reset on tag/search change prevents stale pagination state.


75-92: LGTM! Data preparation is efficient.

Server-side filtering is correctly leveraged, and the memoized grouping logic efficiently prepares data for the grouped view with appropriate dependencies.


94-113: LGTM! ArticleCard component is well-implemented.

The component correctly constructs tag-aware URLs and provides consistent article card rendering throughout the page.


115-118: LGTM! Loading state handled appropriately.

The skeleton provides good UX during initial load.


120-128: LGTM! Page header is well-structured.

Title and description are properly centered and styled.


164-170: LGTM! Empty state is accessible.

Proper use of role="status" for screen readers.


171-187: LGTM! Paginated tag view correctly implements PR objectives.

The active tag is displayed as a heading and passed to ArticleCard (line 182), ensuring article URLs preserve tag context as required.


188-213: LGTM! Grouped view correctly implements PR objectives.

Each tag section shows exactly 3 preview cards with a "View all" link to the filtered paginated view. Tag context is preserved in article URLs.


249-256: LGTM! Component structure is complete.

Proper closing with Banner component at the bottom.

Copy link
Member

@0xApotheosis 0xApotheosis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird tab bahavior: https://jam.dev/c/676eac47-c855-4d8b-bd81-f35aee5887d2

They should probably behave more like radio buttons, rather than having all unselected options disappear.

Search input needs debouncing or other optimizations: https://jam.dev/c/70c35a1c-ea7d-4368-af07-b152736ba268

Space poorly used on categories with only 1 article:

Image

@thesmithdao thesmithdao marked this pull request as draft October 9, 2025 11:39
@thesmithdao
Copy link
Collaborator Author

I'm converting to draft so I can work on those comments =)

@thesmithdao thesmithdao marked this pull request as ready for review October 11, 2025 14:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd05b9 and c9870c4.

📒 Files selected for processing (11)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (5 hunks)
  • app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2 hunks)
  • app/[lang]/(resources)/support/[slug]/page.tsx (2 hunks)
  • app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1 hunks)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx (1 hunks)
  • app/[lang]/_components/SearchBar.tsx (2 hunks)
  • app/[lang]/_components/strapi/types.ts (1 hunks)
  • app/[lang]/_contexts/CachedArticlesContext.tsx (5 hunks)
  • app/[lang]/_hooks/useFetchSupportArticles.ts (4 hunks)
  • app/layout.tsx (3 hunks)
  • tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/layout.tsx
  • app/[lang]/_contexts/CachedArticlesContext.tsx
  • tsconfig.json
  • app/[lang]/_components/SearchBar.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
app/[lang]/_hooks/useFetchSupportArticles.ts (2)
app/[lang]/_components/strapi/types.ts (3)
  • TSupportArticle (303-317)
  • TPagination (125-130)
  • TArticleListResponse (319-324)
app/[lang]/_contexts/CachedArticlesContext.tsx (1)
  • useCachedArticles (114-121)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-43)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
app/[lang]/(resources)/support/[slug]/page.tsx (3)
app/[lang]/_utils/schema.ts (1)
  • generateSupportArticleSchema (112-138)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)
  • SupportArticleClient (9-27)
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (1)
  • SupportArticleContent (15-237)

Copy link
Collaborator Author

@thesmithdao thesmithdao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the support pages and a few helpers: in SupportArticleList.tsx so server queries use the configured pageSize), added an allTags discovery/merge flow so the tag bar behaves like radio buttons (active tag stays visible); in SupportArticleContent.tsx I tightened types by replacing any usages (cast components to Components and safely read image src without any).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
app/[lang]/(resources)/support/_components/SupportTags.tsx (2)

21-28: Consider adding accessibility attributes for active state.

The "All" button would benefit from aria-current="page" or aria-pressed when active to better communicate state to assistive technologies.

Apply this diff to improve accessibility:

 <button
 	type={'button'}
 	onClick={() => onClick(null)}
+	aria-pressed={active === null || active === undefined}
 	className={`rounded-full border text-sm whitespace-nowrap px-3 py-1.5 sm:px-4 sm:py-2 ${
 		active === null || active === undefined ? 'bg-blue' : 'bg-transparent'
 	}`}>
 	{'All'}
 </button>

29-39: Consider adding accessibility attributes for tag buttons.

Similar to the "All" button, tag filter buttons would benefit from aria-pressed to communicate active state to screen readers.

Apply this diff:

 <button
 	type={'button'}
 	key={t}
 	onClick={() => onClick(t)}
+	aria-pressed={active === t}
 	className={`rounded-full border text-sm whitespace-nowrap px-3 py-1.5 sm:px-4 sm:py-2 ${
 		active === t ? 'bg-blue' : 'bg-transparent'
 	}`}>
 	{t}
 </button>
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (1)

71-95: Consider extracting language detection logic.

The code block renderer has inline language detection (/language-(\w+)/.exec(className || '')) that could be extracted to a helper function for better testability and reusability.

Example refactor:

function extractLanguage(className?: string): string | null {
	if (!className) return null
	const match = /language-(\w+)/.exec(className)
	return match ? match[1] : null
}

Then use in the renderer:

-const match = /language-(\w+)/.exec(className || '')
+const language = extractLanguage(className)
-return match ? (
+return language ? (
 	<div className={'relative'}>
-		<div className={'absolute right-2 top-2 text-xs text-gray-400'}>{match[1]}</div>
+		<div className={'absolute right-2 top-2 text-xs text-gray-400'}>{language}</div>
app/[lang]/(resources)/_components/SupportArticleList.tsx (1)

239-240: Consider documenting the 9-item limit.

The .slice(0, 9) limit for preview articles is hardcoded without explanation. Consider extracting this to a named constant or adding a comment explaining why 9 items (likely to match a 3-column grid).

Example:

const PREVIEW_LIMIT = 9 // 3 rows × 3 columns in desktop grid

Then use:

-	.slice(0, 9)
+	.slice(0, PREVIEW_LIMIT)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c9870c4 and 058079e.

📒 Files selected for processing (4)
  • app/[lang]/(resources)/_components/SupportArticleList.tsx (5 hunks)
  • app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2 hunks)
  • app/[lang]/(resources)/support/[slug]/page.tsx (2 hunks)
  • app/[lang]/(resources)/support/_components/SupportTags.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/[lang]/(resources)/_components/SupportArticleList.tsx (4)
app/[lang]/_hooks/useFetchSupportArticles.ts (1)
  • useFetchSupportArticles (29-144)
app/[lang]/_components/strapi/types.ts (1)
  • TSupportArticle (303-317)
app/[lang]/(resources)/support/_components/SupportTags.tsx (1)
  • SupportTags (3-43)
app/[lang]/_components/SearchBar.tsx (1)
  • SearchBar (9-23)
app/[lang]/(resources)/support/[slug]/page.tsx (3)
app/[lang]/_utils/schema.ts (1)
  • generateSupportArticleSchema (112-138)
app/[lang]/(resources)/support/_components/SupportArticleClient.tsx (1)
  • SupportArticleClient (9-27)
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (1)
  • SupportArticleContent (30-325)
🔇 Additional comments (9)
app/[lang]/(resources)/support/[slug]/SupportArticleContent.tsx (2)

18-28: LGTM: sanitizeParams implementation addresses past concerns.

The helper function correctly filters out react-markdown internal props (node, inline, index, children) before spreading onto DOM elements, addressing the previous concern about unknown props causing React warnings.


138-171: LGTM: Image renderer correctly handles src destructuring.

The fix from the previous review has been properly applied—src and alt are extracted from the params object and not included in the rest spread, ensuring Next/Image receives only the sanitized srcString and altText without the original values overwriting them.

app/[lang]/(resources)/_components/SupportArticleList.tsx (3)

224-250: LGTM: Active tag rendering correctly handles single-tag view.

The conditional branch properly renders a flat grid when activeTag is active, preventing the duplication issue flagged in previous comments. The fallback to filteredArticles when groupedArticles[activeTag] is undefined ensures edge cases are handled.


303-303: LGTM: Pagination correctly shown only for tag-filtered views.

The condition activeTag && pagination && pagination.pageCount > 1 properly limits pagination to the paginated tag view, preventing it from appearing in the grouped preview mode as intended by the PR objectives.


109-142: No sequential fetch: discovery prefetch runs in parallel
The discovery hook’s skip: allTags !== null check only gates subsequent calls—on mount both the filtered and unfiltered fetches fire concurrently when allTags is null, so there’s no user-fetch → discovery-fetch waterfall.

app/[lang]/(resources)/support/[slug]/page.tsx (4)

21-24: LGTM: Slug validation properly prevents injection.

The regex validation (/^[A-Za-z0-9-]+$/) and early notFound() return correctly address the security concern from the previous review, ensuring only valid slugs proceed to the API call.


28-30: LGTM: Configuration error handling improved.

Throwing an explicit Error instead of calling notFound() ensures configuration issues surface during build/deployment rather than appearing as 404s to users, as recommended in the previous review.


35-39: LGTM: Cache control prevents stale content.

The cache: 'no-store' option ensures support articles are always fetched fresh from Strapi, addressing the concern from the previous review about default force-cache behavior in app router server components.


64-72: Verify Suspense boundary placement for optimal loading UX.

The Suspense wrapper around SupportArticleClient provides a loading skeleton for the back button and tag label, which is good. However, confirm that the main article content (lines 74-77) doesn't cause layout shift while the server component is rendering, since it's outside the Suspense boundary.

Based on the server component architecture, the entire page should render server-side before streaming to the client, so the article content should be immediately available. Verify this behavior in your testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants