Skip to content

feat(blog): add blog system, sitemap automation, and cookie fixes#131

Merged
omsherikar merged 6 commits intomainfrom
feat/blog-system-sitemap-automation
Mar 31, 2026
Merged

feat(blog): add blog system, sitemap automation, and cookie fixes#131
omsherikar merged 6 commits intomainfrom
feat/blog-system-sitemap-automation

Conversation

@omsherikar
Copy link
Copy Markdown
Collaborator

@omsherikar omsherikar commented Mar 31, 2026

  • Replace Case Studies with full Blog system (BlogPage, BlogPostPage)
  • Add freeform body parser (parseBody) so authors paste raw text
  • Bento grid for featured posts + masonry grid for regular posts
  • Functional search bar across all posts
  • Add 8 blog posts across Product, Case Study, Engineering, Industry Analysis, Developer Guide categories
  • Add automated sitemap generation via scripts/generate-sitemap.js (prebuild hook)
  • Fix cookie consent: gate Vercel Analytics behind analytics preference
  • Simplify CookieConsent, CookieManager, useCookieConsent
  • Add status badge to Footer

Summary by CodeRabbit

  • New Features

    • Full blog section: searchable listing, featured grid, and individual post pages with structured content
    • Automated sitemap generation (keeps sitemap current with blog posts)
  • Updates

    • Navigation and footer now link to the blog; routing updated to support blog URLs
    • Analytics events are gated by stored cookie preferences when sending telemetry
  • Removed

    • Case studies pages and related data removed from the public site

- Replace Case Studies with full Blog system (BlogPage, BlogPostPage)
- Add freeform body parser (parseBody) so authors paste raw text
- Bento grid for featured posts + masonry grid for regular posts
- Functional search bar across all posts
- Add 8 blog posts across Product, Case Study, Engineering, Industry Analysis, Developer Guide categories
- Add automated sitemap generation via scripts/generate-sitemap.js (prebuild hook)
- Fix cookie consent: gate Vercel Analytics behind analytics preference
- Simplify CookieConsent, CookieManager, useCookieConsent
- Add status badge to Footer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 31, 2026 19:49
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
refactron Ready Ready Preview, Comment Mar 31, 2026 8:17pm

@github-actions github-actions bot added type:refactor Code refactoring dependencies Pull requests that update a dependency file type:feature New feature labels Mar 31, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 31, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 07498fb.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@types/node 24.12.0 🟢 6.5
Details
CheckScoreReason
Code-Review🟢 8Found 25/30 approved changesets -- score normalized to 8
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy🟢 10security policy file detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 9license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Binary-Artifacts🟢 10no binaries found in the repo
Fuzzing⚠️ 0project is not fuzzed
npm/@types/sax 1.2.7 🟢 6.5
Details
CheckScoreReason
Code-Review🟢 8Found 25/30 approved changesets -- score normalized to 8
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy🟢 10security policy file detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 9license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Binary-Artifacts🟢 10no binaries found in the repo
Fuzzing⚠️ 0project is not fuzzed
npm/sax 1.6.0 🟢 4.4
Details
CheckScoreReason
Maintained🟢 89 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 8
Code-Review⚠️ 1Found 5/30 approved changesets -- score normalized to 1
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy🟢 10security policy file detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/sitemap 9.0.1 🟢 3.4
Details
CheckScoreReason
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review⚠️ 0Found 0/11 approved changesets -- score normalized to 0
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Maintained🟢 67 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 6
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/undici-types 7.16.0 🟢 8
Details
CheckScoreReason
Dependency-Update-Tool🟢 10update tool detected
Maintained🟢 1030 commit(s) and 14 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 7Found 21/27 approved changesets -- score normalized to 7
Security-Policy🟢 9security policy file detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 8binaries present in source code
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies🟢 4dependency not pinned by hash detected -- score normalized to 4
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ -1no releases found
SAST🟢 9SAST tool detected but not run on all commits
Packaging🟢 10packaging workflow detected
License🟢 10license file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Fuzzing🟢 10project is fuzzed
CI-Tests🟢 1024 out of 24 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 83 contributing companies or organizations

Scanned Files

  • package-lock.json

@github-actions
Copy link
Copy Markdown

👋 Thanks for opening this pull request! A maintainer will review it soon. Please make sure all CI checks pass.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@omsherikar has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 16 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 16 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 288e6ee7-6fbd-40ab-b623-6c8329c2f319

📥 Commits

Reviewing files that changed from the base of the PR and between c9e4acd and 07498fb.

📒 Files selected for processing (6)
  • public/sitemap.xml
  • scripts/generate-sitemap.js
  • src/App.tsx
  • src/components/BlogPage.tsx
  • src/components/BlogPostPage.tsx
  • src/data/posts.ts
📝 Walkthrough

Walkthrough

The PR replaces the case-study section with a blog system: removes case study data/components, adds blog data/components and routing, introduces sitemap generation and prebuild integration, updates prerendering to include blog posts, and simplifies cookie-consent analytics handling.

Changes

Cohort / File(s) Summary
Blog feature
src/components/BlogPage.tsx, src/components/BlogPostPage.tsx, src/data/posts.ts
Added blog listing and post-detail pages plus posts.ts data/module with parser, blog post entries, and lookup helper.
Removed case-study infra
src/components/CaseStudiesPage.tsx, src/components/CaseStudyDetailPage.tsx, src/data/caseStudies.ts
Deleted case study pages, types, data arrays, and lookup functions.
Routing & UI links
src/App.tsx, src/components/NavigationBar.tsx, src/components/Footer.tsx
Repointed routes/links from /case-studies to /blog and swapped case-study components for blog components; added Analytics beforeSend hook reading cookie preferences.
Sitemap & build scripts
package.json, scripts/generate-sitemap.js, public/sitemap.xml, scripts/prerender.js
Added sitemap devDependency and prebuild script; new generator creates sitemap from static routes + post slugs; updated sitemap content; prerender now expands pages with /blog/<slug> entries.
Cookie consent & hooks
src/components/CookieConsent.tsx, src/components/CookieManager.tsx, src/components/CookiePreferencesModal.tsx, src/hooks/useCookieConsent.ts
Simplified consent flow: removed explicit reject callback and analytics enable/disable logic, wired handlers to persist preferences only, and updated modal info styling.
Minor UI changes
src/components/Footer.tsx (also referenced above)
Updated Resources link label and href to point to blog.

Sequence Diagram

sequenceDiagram
    participant User
    participant Router
    participant BlogPage
    participant BlogPostPage
    participant PostsModule as posts.ts

    User->>Router: GET /blog
    Router->>BlogPage: render
    BlogPage->>PostsModule: fetch all posts
    PostsModule-->>BlogPage: return blogPosts[]
    BlogPage-->>User: show listing (search/filter)

    User->>BlogPage: click post card
    Router->>BlogPostPage: navigate /blog/:slug
    BlogPostPage->>PostsModule: getBlogPostBySlug(slug)
    PostsModule-->>BlogPostPage: return post
    BlogPostPage->>BlogPostPage: parseBody(post.body)
    BlogPostPage-->>User: render post content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped from case studies into blog-land bright,
I seeded slugs and sitemaps late at night,
Routes switched smooth, prerender runs the race,
Cookies trimmed of tracking, tidy in place,
A little rabbit cheers: new posts take flight! ✨📬

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(blog): add blog system, sitemap automation, and cookie fixes' accurately summarizes the three main changes: introducing a blog system (replacing case studies), adding automated sitemap generation, and fixing cookie consent behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/blog-system-sitemap-automation

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
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Replaces the existing Case Studies content with a full Blog system, adds automated sitemap generation, and updates cookie consent handling to gate Vercel Analytics behind analytics consent.

Changes:

  • Added Blog listing + Blog post detail pages, plus a post data source and lightweight body parser.
  • Added a prebuild sitemap generator to automatically include blog post routes.
  • Simplified cookie consent flow and gated Vercel Analytics events based on stored cookie preferences.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/hooks/useCookieConsent.ts Simplifies consent persistence logic and removes in-hook analytics toggling.
src/data/posts.ts Adds blog post data + parseBody() to render freeform post content.
src/data/caseStudies.ts Removes Case Studies data source.
src/components/NavigationBar.tsx Updates nav link from Case Studies to Blog.
src/components/Footer.tsx Updates footer link from Case Studies/Examples to Blog.
src/components/CookiePreferencesModal.tsx Tweaks consent modal styling (info box colors).
src/components/CookieManager.tsx Simplifies banner handlers and wiring into savePreferences.
src/components/CookieConsent.tsx Removes explicit reject callback and routes reject into onAccept w/ disabled prefs.
src/components/CaseStudyDetailPage.tsx Removes Case Study detail page.
src/components/CaseStudiesPage.tsx Removes Case Studies listing page.
src/components/BlogPostPage.tsx Adds blog post detail rendering (icons + parsed body sections).
src/components/BlogPage.tsx Adds blog listing page w/ featured bento grid, masonry list, and search.
src/App.tsx Switches routes from /case-studies to /blog and gates Analytics with beforeSend.
scripts/generate-sitemap.js Adds automated sitemap generation from blog post slugs.
public/sitemap.xml Replaces static sitemap with generated sitemap output including blog URLs.
package.json Adds sitemap dependency and prebuild hook to generate sitemap.
package-lock.json Locks sitemap and transitive dependencies (incl. engine constraints).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 40 to 49
"devDependencies": {
"@sparticuz/chromium": "^143.0.4",
"prettier": "^3.6.2",
"puppeteer-core": "^24.40.0"
"puppeteer-core": "^24.40.0",
"sitemap": "^9.0.1"
},
"scripts": {
"start": "react-scripts start",
"prebuild": "node scripts/generate-sitemap.js",
"build": "react-scripts build",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The added sitemap@^9.0.1 devDependency declares an engines requirement of node >=20.19.5 (see package-lock), but this repo’s CI matrix includes Node 16.x and 18.x. This is likely to break npm ci and/or npm run build in those environments because prebuild runs the sitemap generator. Consider pinning sitemap to a version that supports the project’s supported Node versions, or updating the supported Node/CI matrix accordingly (and documenting it).

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +24
const staticRoutes = [
{ url: '/', changefreq: 'weekly', priority: 1.0 },
{ url: '/blog', changefreq: 'weekly', priority: 0.9 },
{ url: '/pricing', changefreq: 'monthly', priority: 0.9 },
{ url: '/about', changefreq: 'monthly', priority: 0.6 },
{ url: '/changelog', changefreq: 'weekly', priority: 0.7 },
{ url: '/security', changefreq: 'monthly', priority: 0.5 },
{ url: '/privacy-policy', changefreq: 'yearly', priority: 0.3 },
{ url: '/terms-of-service',changefreq: 'yearly', priority: 0.3 },
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

/pricing is included as a static sitemap route, but the app routes in src/App.tsx do not define a /pricing page (pricing appears to be a section on the landing page). This will publish a broken URL in sitemap.xml and hurt SEO/crawl quality. Either remove /pricing from the sitemap or add a real /pricing route/page.

Copilot uses AI. Check for mistakes.
textBuffer.push(line);
}
}

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

parseBody drops any trailing code block if the input ends while insideCode is still true (i.e., an unclosed ``` fence). That results in losing content and can render posts incomplete. Consider flushing codeLines at EOF when `insideCode` is still true (either as a `code` section or as a paragraph fallback).

Suggested change
if (insideCode && codeLines.length > 0) {
sections.push({ type: 'code', text: codeLines.join('\n') });
}

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +15
function getIcon(industry: string) {
const attr = {
stroke: 'rgba(0,0,0,0.45)',
strokeWidth: 1.5,
fill: 'none' as const,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
};
const lower = industry.toLowerCase();
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

getIcon() is implemented locally in this file but appears to be essentially duplicated in BlogPage.tsx. Keeping two copies of the same category→SVG mapping will likely drift over time. Consider extracting this into a shared utility/component (e.g., src/components/blog/blogIcons.tsx) and reusing it in both pages.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +15
// Minimal line-art SVG icons keyed by industry
function getIcon(industry: string, size = 64) {
const attr = {
stroke: 'rgba(0,0,0,0.45)',
strokeWidth: 1.5,
fill: 'none' as const,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
};
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

getIcon() is implemented locally here and is essentially duplicated in BlogPostPage.tsx. To avoid mismatches (e.g., different icon selection rules or sizes per category), consider extracting a shared icon renderer/util and importing it in both files.

Copilot uses AI. Check for mistakes.
- Remove stale /case-studies route
- Add /blog, /changelog, /security to static pre-render list
- Dynamically read blog slugs from posts.ts so every blog post
  gets a pre-rendered index.html for crawlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 8

🧹 Nitpick comments (3)
package.json (1)

43-48: Avoid regex-scanning raw post source for sitemap slugs.

prebuild now hard-depends on scripts/generate-sitemap.js, and that script extracts every slug: match from the raw src/data/posts.ts text. With the new freeform post bodies, a pasted code/frontmatter snippet containing slug: "..." can generate bogus sitemap URLs. Please switch sitemap generation to a structured export/manifest rather than scanning the source file.

src/components/BlogPage.tsx (1)

7-195: Extract getIcon into a shared module to avoid drift.

This icon-selection logic is duplicated in src/components/BlogPostPage.tsx (Line 7 onward). Keeping one shared helper/component will prevent mismatched icon behavior over time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BlogPage.tsx` around lines 7 - 195, The getIcon function is
duplicated; extract the getIcon implementation into a single shared module
(e.g., export function getIcon(...) from a new file) and replace the inline
copies in BlogPage.tsx and BlogPostPage.tsx with an import of that shared
getIcon; ensure the new module exports the same named function signature and
preserves the default size parameter and returned JSX so both files call
getIcon(industry, size) unchanged, update imports in both components, and remove
the duplicated function bodies.
scripts/generate-sitemap.js (1)

13-15: Harden slug extraction to avoid false sitemap URLs.

Line 13 currently matches any slug: token in the file, including potential future prose/code snippets in body content. Anchor matching to property lines and dedupe before route generation.

Proposed fix
-const slugMatches = [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)];
-const blogSlugs = slugMatches.map(m => m[1]);
+const slugMatches = [
+  ...postsFile.matchAll(/^\s*slug:\s*['"]([^'"]+)['"],?\s*$/gm),
+];
+const blogSlugs = [...new Set(slugMatches.map(([, slug]) => slug.trim()))];

Also applies to: 28-32

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-sitemap.js` around lines 13 - 15, The slug extraction
currently uses postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g) which can match
`slug:` occurrences inside body text; update the regex to anchor to the start of
a property line (e.g. use /(?:^|\n)\s*slug:\s*['"]([^'"]+)['"]/gm or
/^\s*slug:\s*['"]([^'"]+)['"]/gm) when building slugMatches, then map to
blogSlugs as before but remove duplicates (e.g. Array.from(new Set(blogSlugs)))
so subsequent route generation uses only true frontmatter slug lines and unique
slugs; apply the same anchored regex + dedupe logic where slugs are extracted
around lines 28-32 as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/App.tsx`:
- Around line 101-116: The new Route entries for Blog (Route path="/blog"
element={<PageLayout><BlogPage/></PageLayout>}) and BlogPost (Route
path="/blog/:slug" element={<PageLayout mainClassName="pt-0
sm:pt-0"><BlogPostPage/></PageLayout>}) remove the legacy /case-studies entry
points and will 404 old links; add compatibility routes that either render the
same components or issue client redirects for the old paths (add Route for
path="/case-studies" that maps to the same PageLayout+BlogPage and Route for
path="/case-studies/:slug" that maps to the same PageLayout+BlogPostPage or use
a Navigate redirect to the corresponding /blog routes) so bookmarks/backlinks
remain valid during migration.

In `@src/components/BlogPage.tsx`:
- Around line 1-427: Prettier format checks are failing for BlogPage.tsx; run
Prettier and reformat the file (ensuring the exported BlogPage component and
related functions like getIcon, getBentoSpan, BentoCard, RegularCard remain
unchanged functionally) so it passes CI --check; typically run your project's
formatter command (e.g., npx prettier --write src/components/BlogPage.tsx or
yarn prettier:fix) and commit the modified file.
- Around line 303-310: In matchPost, normalize the incoming search string by
trimming whitespace before lowercasing so leading/trailing spaces don't cause
false negatives; replace the current q assignment (which uses
query.toLowerCase()) with one that does query.trim().toLowerCase() and use that
q in the existing checks (keep references to p.title, p.industry, p.summary, and
p.tags.some(...) unchanged); optionally short-circuit if q is empty to avoid
unnecessary work.
- Around line 213-220: The clickable non-semantic motion.div should be replaced
with a semantic, keyboard-accessible motion.button: change the motion.div
element (the one using initial/whileInView/viewport/transition, onClick={() =>
navigate(`/blog/${post.slug}`)}, className and props like index and wide) to
motion.button, remove any invalid button-only props if present, ensure the
navigate(`/blog/${post.slug}`) call remains in the onClick, and preserve the
animation props (initial, whileInView, viewport, transition) and className so
the visual behavior is identical while gaining native keyboard focus and
activation support.

In `@src/components/BlogPostPage.tsx`:
- Around line 1-319: The file src/components/BlogPostPage.tsx fails Prettier
formatting in CI; run Prettier on this file (e.g. prettier --write
src/components/BlogPostPage.tsx or your project’s prettier/npm script) to fix
whitespace/format issues and re-commit so the CI --check passes; focus on
formatting the exported BlogPostPage component and helper functions like getIcon
and renderSection, then re-run the Prettier check before pushing.

In `@src/data/posts.ts`:
- Around line 1-734: Prettier is flagging formatting drift in this file; run the
project's Prettier formatter against this file (or the whole repo) and commit
the resulting changes to satisfy CI. Locate the top-level exports (parseBody,
blogPosts array, and getBlogPostBySlug) to confirm Prettier's changes didn't
alter logic; run your configured command (e.g., npm/yarn prettier --write or the
repo's format script) and add the formatted file to the PR, then push so the CI
Prettier check passes.
- Around line 74-93: The loop in posts.ts can end with insideCode === true which
loses accumulated codeLines; update the EOF handling so any unterminated code
block is emitted before returning: after the for-loop (before return sections)
detect if insideCode is true and push a section like sections.push({ type:
'code', text: codeLines.join('\n') }) (or modify flushText to handle both
textBuffer and codeLines), then clear state so no content is dropped; reference
the variables insideCode, codeLines, sections and the flushText helper when
making the change.

In `@src/hooks/useCookieConsent.ts`:
- Around line 51-68: The hook's savePreferences only updates local state and
localStorage for that hook instance (savePreferences in useCookieConsent) so
separate consumers (Footer.tsx and CookieManager.tsx) don't sync; fix by making
the cookie state shared across components either by (A) lifting state into a
CookieConsentProvider: move preferences, hasConsent, setPreferences,
setHasConsent, and savePreferences into a React context/provider and have
useCookieConsent read/write from that context so all consumers subscribe to the
same state, or (B) if you prefer minimal change, broadcast updates inside
savePreferences via window.dispatchEvent(new
CustomEvent('cookie-preferences-changed', { detail: newPreferences })) and add a
window.addEventListener('cookie-preferences-changed', ...) in useCookieConsent
to update setPreferences and setHasConsent when events arrive; update Footer.tsx
and CookieManager.tsx to use the provider or rely on the event so banner/modal
reflect changes immediately.

---

Nitpick comments:
In `@scripts/generate-sitemap.js`:
- Around line 13-15: The slug extraction currently uses
postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g) which can match `slug:`
occurrences inside body text; update the regex to anchor to the start of a
property line (e.g. use /(?:^|\n)\s*slug:\s*['"]([^'"]+)['"]/gm or
/^\s*slug:\s*['"]([^'"]+)['"]/gm) when building slugMatches, then map to
blogSlugs as before but remove duplicates (e.g. Array.from(new Set(blogSlugs)))
so subsequent route generation uses only true frontmatter slug lines and unique
slugs; apply the same anchored regex + dedupe logic where slugs are extracted
around lines 28-32 as well.

In `@src/components/BlogPage.tsx`:
- Around line 7-195: The getIcon function is duplicated; extract the getIcon
implementation into a single shared module (e.g., export function getIcon(...)
from a new file) and replace the inline copies in BlogPage.tsx and
BlogPostPage.tsx with an import of that shared getIcon; ensure the new module
exports the same named function signature and preserves the default size
parameter and returned JSX so both files call getIcon(industry, size) unchanged,
update imports in both components, and remove the duplicated function bodies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cacdfa9-f278-4d88-ac95-74d55938e879

📥 Commits

Reviewing files that changed from the base of the PR and between bf688f0 and c88e3b6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • package.json
  • public/sitemap.xml
  • scripts/generate-sitemap.js
  • src/App.tsx
  • src/components/BlogPage.tsx
  • src/components/BlogPostPage.tsx
  • src/components/CaseStudiesPage.tsx
  • src/components/CaseStudyDetailPage.tsx
  • src/components/CookieConsent.tsx
  • src/components/CookieManager.tsx
  • src/components/CookiePreferencesModal.tsx
  • src/components/Footer.tsx
  • src/components/NavigationBar.tsx
  • src/data/caseStudies.ts
  • src/data/posts.ts
  • src/hooks/useCookieConsent.ts
💤 Files with no reviewable changes (3)
  • src/components/CaseStudiesPage.tsx
  • src/components/CaseStudyDetailPage.tsx
  • src/data/caseStudies.ts

Comment on lines +213 to +220
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.08 }}
onClick={() => navigate(`/blog/${post.slug}`)}
className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col ${wide ? 'md:col-span-2' : 'md:col-span-1'}`}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "BlogPage.tsx" | head -20

Repository: Refactron-ai/Refactron_Website

Length of output: 103


🏁 Script executed:

wc -l ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 107


🏁 Script executed:

sed -n '210,225p' ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 698


🏁 Script executed:

sed -n '255,270p' ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 686


🏁 Script executed:

sed -n '200,235p' ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 1345


🏁 Script executed:

sed -n '245,280p' ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 1262


🏁 Script executed:

head -50 ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 1615


🏁 Script executed:

grep -n "motion\." ./src/components/BlogPage.tsx | head -30

Repository: Refactron-ai/Refactron_Website

Length of output: 253


🏁 Script executed:

grep -n "role\|aria-\|tabIndex\|onKeyDown\|onKeyUp" ./src/components/BlogPage.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 56


🏁 Script executed:

web_search
Does Framer Motion support motion.button component?

Repository: Refactron-ai/Refactron_Website

Length of output: 167


🌐 Web query:

Framer Motion motion.button component support

💡 Result:

Yes—motion.button is supported.

Motion (formerly Framer Motion) provides a motion. version of every HTML/SVG element, so you can use <motion.button> as a drop-in replacement for <button> and add animation/gesture props like initial, animate, whileHover, whileTap, exit, etc. [1], [2]

Example:

import { motion } from "motion/react"

export function MyButton() {
  return (
    <motion.button
      initial={{ scale: 0 }}
      animate={{ scale: 1 }}
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
    >
      Click
    </motion.button>
  )
}

[1]

If you meant a custom Button component (not a native <button>), wrap it with motion.create() (it must forward its ref and accept a style prop). [2]

Sources: [1] [2]


Replace non-semantic clickable divs with keyboard-accessible button elements.

Lines 213 and 257 use motion.div with onClick handlers, which are not keyboard-operable. This blocks keyboard-only and screen reader users from navigating to posts. Replace with motion.button to provide native keyboard support (Tab focus and Enter/Space activation).

Proposed fix
-    <motion.div
+    <motion.button
+      type="button"
       initial={{ opacity: 0, y: 30 }}
       whileInView={{ opacity: 1, y: 0 }}
       viewport={{ once: true }}
       transition={{ duration: 0.5, delay: index * 0.08 }}
       onClick={() => navigate(`/blog/${post.slug}`)}
-      className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col ${wide ? 'md:col-span-2' : 'md:col-span-1'}`}
+      className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col text-left ${wide ? 'md:col-span-2' : 'md:col-span-1'}`}
     >
@@
-    </motion.div>
+    </motion.button>
-    <motion.div
+    <motion.button
+      type="button"
       initial={{ opacity: 0, y: 24 }}
       whileInView={{ opacity: 1, y: 0 }}
       viewport={{ once: true }}
       transition={{ duration: 0.4, delay: index * 0.07 }}
       onClick={() => navigate(`/blog/${post.slug}`)}
-      className="group rounded-2xl overflow-hidden cursor-pointer flex flex-col bg-[`#111111`] hover:bg-[`#161616`] transition-colors break-inside-avoid mb-4"
+      className="group rounded-2xl overflow-hidden cursor-pointer flex flex-col bg-[`#111111`] hover:bg-[`#161616`] transition-colors break-inside-avoid mb-4 text-left w-full"
     >
@@
-    </motion.div>
+    </motion.button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BlogPage.tsx` around lines 213 - 220, The clickable
non-semantic motion.div should be replaced with a semantic, keyboard-accessible
motion.button: change the motion.div element (the one using
initial/whileInView/viewport/transition, onClick={() =>
navigate(`/blog/${post.slug}`)}, className and props like index and wide) to
motion.button, remove any invalid button-only props if present, ensure the
navigate(`/blog/${post.slug}`) call remains in the onClick, and preserve the
animation props (initial, whileInView, viewport, transition) and className so
the visual behavior is identical while gaining native keyboard focus and
activation support.

Comment on lines +303 to +310
const matchPost = (p: BlogPost) => {
const q = query.toLowerCase();
return (
p.title.toLowerCase().includes(q) ||
p.industry.toLowerCase().includes(q) ||
p.summary.toLowerCase().includes(q) ||
p.tags.some(t => t.toLowerCase().includes(q))
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize the search query before matching.

Line 304 uses the raw query for filtering. Leading/trailing spaces can produce false negatives even when isSearching is true.

Proposed fix
-    const q = query.toLowerCase();
+    const q = query.trim().toLowerCase();
📝 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.

Suggested change
const matchPost = (p: BlogPost) => {
const q = query.toLowerCase();
return (
p.title.toLowerCase().includes(q) ||
p.industry.toLowerCase().includes(q) ||
p.summary.toLowerCase().includes(q) ||
p.tags.some(t => t.toLowerCase().includes(q))
);
const matchPost = (p: BlogPost) => {
const q = query.trim().toLowerCase();
return (
p.title.toLowerCase().includes(q) ||
p.industry.toLowerCase().includes(q) ||
p.summary.toLowerCase().includes(q) ||
p.tags.some(t => t.toLowerCase().includes(q))
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BlogPage.tsx` around lines 303 - 310, In matchPost, normalize
the incoming search string by trimming whitespace before lowercasing so
leading/trailing spaces don't cause false negatives; replace the current q
assignment (which uses query.toLowerCase()) with one that does
query.trim().toLowerCase() and use that q in the existing checks (keep
references to p.title, p.industry, p.summary, and p.tags.some(...) unchanged);
optionally short-circuit if q is empty to avoid unnecessary work.

Comment on lines +74 to +93
for (const line of lines) {
if (line.trimStart().startsWith('```')) {
if (!insideCode) {
flushText();
insideCode = true;
codeLines = [];
} else {
sections.push({ type: 'code', text: codeLines.join('\n') });
insideCode = false;
codeLines = [];
}
} else if (insideCode) {
codeLines.push(line);
} else {
textBuffer.push(line);
}
}

flushText();
return sections;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unterminated code fences drop content at EOF.

If input ends while insideCode is true, accumulated codeLines are never emitted. That silently loses author content.

Proposed fix
   for (const line of lines) {
@@
   }
 
+  if (insideCode) {
+    sections.push({ type: 'code', text: codeLines.join('\n') });
+  }
   flushText();
   return sections;
 }
📝 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.

Suggested change
for (const line of lines) {
if (line.trimStart().startsWith('```')) {
if (!insideCode) {
flushText();
insideCode = true;
codeLines = [];
} else {
sections.push({ type: 'code', text: codeLines.join('\n') });
insideCode = false;
codeLines = [];
}
} else if (insideCode) {
codeLines.push(line);
} else {
textBuffer.push(line);
}
}
flushText();
return sections;
for (const line of lines) {
if (line.trimStart().startsWith('
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/data/posts.ts` around lines 74 - 93, The loop in posts.ts can end with
insideCode === true which loses accumulated codeLines; update the EOF handling
so any unterminated code block is emitted before returning: after the for-loop
(before return sections) detect if insideCode is true and push a section like
sections.push({ type: 'code', text: codeLines.join('\n') }) (or modify flushText
to handle both textBuffer and codeLines), then clear state so no content is
dropped; reference the variables insideCode, codeLines, sections and the
flushText helper when making the change.

Comment on lines +51 to +68
const savePreferences = useCallback((newPreferences: CookiePreferences) => {
try {
localStorage.setItem(
'cookie-preferences',
JSON.stringify(newPreferences)
);
localStorage.setItem('cookie-consent', 'true');
localStorage.setItem('cookie-consent-date', new Date().toISOString());

// Trigger analytics based on preferences
handleAnalyticsConsent(newPreferences.analytics);
setPreferences(newPreferences);
setHasConsent(true);

return true;
} catch (error) {
console.error('Error saving cookie preferences:', error);
return false;
}
},
[handleAnalyticsConsent]
);
return true;
} catch (error) {
console.error('Error saving cookie preferences:', error);
return false;
}
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

useCookieConsent() is not synchronized across components.

savePreferences updates this hook instance plus localStorage only. Since Footer.tsx Line 61 and CookieManager.tsx Lines 7-8 each call useCookieConsent() separately, saving from one surface does not update the other in the same tab. The banner can stay visible after saving from the footer modal, and the footer can reopen with stale values after accepting from the banner. This state needs to live in a shared provider or broadcast an explicit in-tab change event.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useCookieConsent.ts` around lines 51 - 68, The hook's
savePreferences only updates local state and localStorage for that hook instance
(savePreferences in useCookieConsent) so separate consumers (Footer.tsx and
CookieManager.tsx) don't sync; fix by making the cookie state shared across
components either by (A) lifting state into a CookieConsentProvider: move
preferences, hasConsent, setPreferences, setHasConsent, and savePreferences into
a React context/provider and have useCookieConsent read/write from that context
so all consumers subscribe to the same state, or (B) if you prefer minimal
change, broadcast updates inside savePreferences via window.dispatchEvent(new
CustomEvent('cookie-preferences-changed', { detail: newPreferences })) and add a
window.addEventListener('cookie-preferences-changed', ...) in useCookieConsent
to update setPreferences and setHasConsent when events arrive; update Footer.tsx
and CookieManager.tsx to use the provider or rely on the event so banner/modal
reflect changes immediately.

- Remove /pricing (section anchor, not a real route)
- Remove /status (internal page, not for indexing)
- Add /status to prerender exclusion
- Regenerate sitemap with correct 7 static + 8 blog routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
scripts/prerender.js (1)

18-25: Consider extracting shared slug-extraction logic.

This slug extraction code is duplicated verbatim in scripts/generate-sitemap.js (lines 8-14). Extracting this to a shared utility (e.g., scripts/lib/extractBlogSlugs.js) would improve maintainability and ensure both scripts stay synchronized if the posts.ts format changes.

♻️ Proposed shared utility

Create scripts/lib/extractBlogSlugs.js:

const fs = require('fs');
const path = require('path');

function extractBlogSlugs() {
  const postsFile = fs.readFileSync(
    path.join(__dirname, '../../src/data/posts.ts'),
    'utf-8'
  );
  return [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)].map(m => m[1]);
}

module.exports = { extractBlogSlugs };

Then in both prerender.js and generate-sitemap.js:

-const postsFile = fs.readFileSync(
-  path.join(__dirname, '../src/data/posts.ts'),
-  'utf-8'
-);
-const blogSlugs = [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)].map(
-  m => m[1]
-);
+const { extractBlogSlugs } = require('./lib/extractBlogSlugs');
+const blogSlugs = extractBlogSlugs();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/prerender.js` around lines 18 - 25, Duplicate slug-extraction logic
exists in scripts/prerender.js and scripts/generate-sitemap.js; extract it to a
shared utility (e.g., create scripts/lib/extractBlogSlugs.js exporting a
function named extractBlogSlugs) that reads ../src/data/posts.ts and returns the
slug array, then replace the inline regex blocks in prerender.js and
generate-sitemap.js to import/require and call extractBlogSlugs() so both
scripts use the single shared implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/prerender.js`:
- Around line 18-25: Duplicate slug-extraction logic exists in
scripts/prerender.js and scripts/generate-sitemap.js; extract it to a shared
utility (e.g., create scripts/lib/extractBlogSlugs.js exporting a function named
extractBlogSlugs) that reads ../src/data/posts.ts and returns the slug array,
then replace the inline regex blocks in prerender.js and generate-sitemap.js to
import/require and call extractBlogSlugs() so both scripts use the single shared
implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec8a1ba0-259c-4f37-849b-53c19d3fa684

📥 Commits

Reviewing files that changed from the base of the PR and between c88e3b6 and c9e4acd.

📒 Files selected for processing (1)
  • scripts/prerender.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Preserve backward compatibility for bookmarks, backlinks, and
indexed pages by redirecting /case-studies → /blog and
/case-studies/:slug → /blog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@omsherikar omsherikar merged commit 135daa7 into main Mar 31, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file type:feature New feature type:refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants