Skip to content

Conversation

@HarshJS30
Copy link

@HarshJS30 HarshJS30 commented Nov 16, 2025

Summary

Fixes #155

This PR implements a complete, production-ready newsletters experience for Pro users on OpenSox.

The goal was to build something clean, scalable, and long-term friendly rather than a one-off implementation.


🎯 Features Implemented

1. Premium-Only Access

  • Non-Pro users: See a dedicated landing page with focused CTA encouraging upgrade
  • Pro users: Full access to newsletters listing, filtering, and article pages
  • Clean access control with graceful fallback

2. Scalable Architecture

Key Design Decision: Metadata/content separation for performance at scale

How it works:

  • Listing page: Only loads lightweight metadata from src/data/newsletters.ts
  • Article page: Full markdown content loaded on-demand from src/content/newsletters/*.md
  • Result: No unnecessary parsing/rendering work

Why this matters:

As newsletters grow to 50+, the listing page stays fast because we're not bundling all markdown content into the client.

src/data/newsletters.ts          → Fast metadata for listings
src/content/newsletters/*.md     → Full content loaded on-demand

This architecture prevents performance degradation as content scales.

3. Newsletter Listing Page

  • ✅ Sorted latest-first by date
  • ✅ Search functionality (by title)
  • ✅ Month filter
  • ✅ Year filter
  • ✅ Combined filtering works smoothly
  • ✅ Responsive layout for all breakpoints
  • ✅ Polished UI with subtle animations

4. Markdown-Based Content System

Tech stack:

  • gray-matter - YAML frontmatter parsing
  • remark - Markdown to HTML conversion
  • Tailwind Typography - Beautiful prose styling

Supported formatting:

  • Headings (h1-h6)
  • Bold text
  • Links
  • Images
  • Paragraphs
  • Lists

Developer benefit: Writing newsletters is as simple as creating a .md file. No UI changes needed for new content.

5. Article Page (Slug-Based Routing)

  • Clean, distraction-free reading experience
  • Cover image with date badge
  • Rendered markdown with Tailwind Typography
  • Mobile-optimized reading interface
  • Proper meta tags for SEO

6. GSAP Page Transitions

  • Smooth animations between newsletter views
  • Premium, intentional navigation feel
  • Non-intrusive, performance-optimized

📁 File Structure

src/
├── app/
│   ├── (main)/newsletters/
│   │   ├── page.tsx                    # Listing page with search/filters
│   │   ├── [slug]/page.tsx             # Individual article view
│   │   └── pagetransition.tsx          # GSAP transition wrapper
│   ├── content/newsletters/            # Markdown articles (sample data)
│   │   ├── getting-started-with-nextjs.md
│   │   ├── mastering-react-hooks.md
│   │   └── understanding-typescript.md
│   └── docs/
│       └── how-to-add-newsletter.md    # Team documentation
├── components/
│   ├── dashboard/
│   │   └── Sidebar.tsx                 # Updated with Newsletters link
│   └── non-pro-news/
│       └── News.tsx                    # Non-Pro landing component
├── data/
│   └── newsletters.ts                  # Metadata array (title, date, slug, etc.)
└── lib/
    └── newslettercontent.ts            # Content loader utility

📝 Content Management Workflow

Adding a new newsletter takes ~2 minutes:

  1. Create new .md file in src/content/newsletters/
  2. Add metadata entry to src/data/newsletters.ts
  3. Commit and deploy

No database. No CMS. No admin panel.

Full step-by-step guide included in: src/app/docs/how-to-add-newsletter.md


✅ Testing Checklist

  • Desktop browsers (Chrome, Firefox, Safari)
  • Mobile responsive design
  • Search functionality
  • Month filter
  • Year filter
  • Combined filters
  • Pro user access
  • Non-Pro landing page
  • Markdown rendering (headings, bold, links, images, lists)
  • Page transitions
  • Individual article slugs
  • Latest-first sorting

🎬 Demo

rec.mp4

📸 Screenshots

Non-Pro Landing Page

Non-Pro users see compelling CTA to upgrade

📦 Sample Data

Three example newsletters included for testing:

Content files:

src/content/newsletters/
├── getting-started-with-nextjs.md
├── mastering-react-hooks.md
└── understanding-typescript.md

Metadata:

src/data/newsletters.ts

All sample newsletters include:

  • Properly formatted markdown
  • Cover images
  • Metadata (title, date, author, excerpt)

💭 Design Decisions

Why Markdown instead of a CMS?

  • ✅ Zero infrastructure overhead
  • ✅ Version controlled content
  • ✅ Fast implementation
  • ✅ Easy migration path if CMS needed later
  • ✅ Developer-friendly workflow

Why separate metadata from content?

  • Performance: Listing page doesn't parse markdown
  • Flexibility: Can add metadata fields without touching content
  • Scalability: Handles 100+ newsletters without slowdown

Future Enhancements (Out of Scope)

Potential additions for later iterations:

  • Category/tag system for newsletters
  • Email subscription integration
  • Reading time estimates
  • Social sharing buttons

📋 Submission Checklist

  • Includes screen recording
  • Sample data (3 newsletters)
  • Documentation on adding newsletters
  • No merge conflicts
  • All requirements met:
    • Organization by date
    • Rich content support (text, links, images)
    • Easy content management through code
    • Newsletter listing page
    • Readable articles
    • Bold and heading support

Closing Note

I've focused on building something genuinely scalable rather than just meeting the minimum requirements. The architecture supports growth without performance degradation, the markdown-based system makes content management trivial, and the code is clean and maintainable.

I've really enjoyed working on this feature, and if things work out, I'd love the opportunity to contribute more and work with you in the future.

Looking forward to your feedback!


Summary by CodeRabbit

  • New Features

    • Newsletters section with searchable, filterable list and subscription-based access
    • Individual newsletter detail pages showing full content, cover image, metadata, and back navigation
    • Smooth page transition animations and responsive mobile-friendly layout
    • New promotional hero/CTA for non-subscribers
    • Added newsletter data store and content loader for Markdown-based posts
  • Documentation

    • Added guides: "Getting Started with Next.js," "Mastering React Hooks," "Understanding TypeScript," and "How to add newsletters"
  • UI

    • Sidebar updated with a Newsletters route and refreshed header/profile UI

@vercel
Copy link

vercel bot commented Nov 16, 2025

@HarshJS30 is attempting to deploy a commit to the AJEET PRATAP SINGH's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

Walkthrough

Adds a newsletters feature: a listing page with filters and pro-only branch, a dynamic slug detail page that loads markdown and renders HTML, a client-side page transition animation, sample newsletter content and docs, data/loader utilities, a non‑pro CTA hero, and package dependency additions.

Changes

Cohort / File(s) Summary
Listing & Detail Pages
apps/web/src/app/(main)/newsletters/page.tsx, apps/web/src/app/(main)/newsletters/[slug]/page.tsx
Adds newsletters listing UI (search, month/year filters, sidebar, paid/non‑paid branches) and a dynamic slug page that loads markdown via getNewsletterContent, returns 404 for missing posts, and renders HTML.
Page Transition
apps/web/src/app/(main)/newsletters/pagetransition.tsx
New client component implementing a 12-block animated overlay; intercepts internal link clicks to perform cover/uncover animations around Next.js router navigation.
Sample Content
apps/web/src/app/content/newsletters/getting-started-with-nextjs.md, apps/web/src/app/content/newsletters/mastering-react-hooks.md, apps/web/src/app/content/newsletters/understanding-typescript.md
Adds three markdown newsletter articles with front matter, code examples, and metadata.
Data & Loader
apps/web/src/data/newsletters.ts, apps/web/src/lib/newslettercontent.ts
Introduces Post interface and posts sample array with images; adds getNewsletterContent(slug: string) to validate slug, read markdown, parse front matter (gray-matter), and convert to HTML (remark + remark-html).
Non‑Pro Hero
apps/web/src/components/non-pro-news/News.tsx
New presentational hero/CTA component linking to /pricing for non-paid users.
Docs
apps/web/src/app/docs/how-to-add-newsletters.md
Documentation describing how to add newsletter metadata, markdown files, image handling, and required file locations.
Sidebar & UI Adjustments
apps/web/src/components/dashboard/Sidebar.tsx
Adds a "Newsletters" route and refactors header/profile dropdown rendering and imports (lucide-react icons), plus styling/class adjustments.
Configuration
apps/web/package.json
Adds runtime dependencies: gray-matter, remark, remark-html, and gsap (and gsap usage for animations presumed).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Listing as NewslettersPage
    participant Transition as PageTransition
    participant Data as posts[] (newsletters.ts)
    participant Loader as getNewsletterContent
    participant FS as File System (markdown)
    participant Renderer as remark -> HTML

    User->>Listing: Open /newsletters
    Listing->>Data: read posts metadata
    Data-->>Listing: posts[]
    Listing->>Listing: compute months/years, apply search/month/year filters
    alt isPaidUser
        Listing->>User: render full listing + sidebar
    else
        Listing->>User: render non‑pro CTA (News)
    end

    User->>Listing: Click post link
    Listing->>Transition: start cover animation (blocks scaleX 0→1)
    Transition->>Listing: on complete -> router.push(/newsletters/[slug])
    User->>Loader: GET /newsletters/[slug]
    Loader->>FS: read `apps/web/src/app/content/newsletters/[slug].md`
    FS-->>Loader: markdown + front matter
    Loader->>Renderer: parse → HTML
    Renderer-->>Loader: htmlContent, meta
    Loader-->>Listing: htmlContent, meta
    Listing->>User: render post (cover image, meta, innerHTML)
    Listing->>Transition: reveal animation (blocks scaleX 1→0)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Review attention:
    • getNewsletterContent: slug sanitization and error handling.
    • page.tsx listing: correctness of filter/search logic and memoization.
    • pagetransition.tsx: event interception conditions, router interaction, and listener cleanup.
    • Sidebar edits: icon imports and route integration.
    • Use of dangerouslySetInnerHTML — consider sanitization where needed.

Possibly related issues

  • #155: [bounty-to-hire]: create ui for newsletter — This PR implements the newsletter listing, detail pages, sample data, and docs matching the issue objectives (organization by date, addable markdown content, and listing).
  • v2-dashboard: create a page for newsletter #122 — Similar newsletter feature work (listing and detail routes); likely addressing overlapping goals.

Possibly related PRs

Poem

🐇
i hop through markdown, nibbling slugs so bright,
blocks that dance across the night,
filters hum and images bloom,
press subscribe and chase the moon,
hop on—newsletters take flight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add newsletters feature' accurately summarizes the main change, which is the implementation of a complete newsletters feature for Pro users including listing, filtering, and article pages.
Linked Issues check ✅ Passed The PR fully implements all core requirements from issue #155: organization by date with month/year filters, rich content support (text, links, images), team-only code-based content management, newsletter listing ordered latest-first, bold text and headings support, and includes sample data, documentation, and screen recording.
Out of Scope Changes check ✅ Passed All changes directly support the newsletters feature implementation, including UI components, data structures, content system, Sidebar integration, and supporting utilities. No unrelated modifications are present.
✨ 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

🧹 Nitpick comments (6)
apps/web/src/data/newsletters.ts (2)

7-14: Minor style inconsistency.

Missing trailing comma after coverimg property on line 12.

Apply this diff:

   image: StaticImageData;
-  coverimg:StaticImageData,
+  coverimg: StaticImageData,
   slug: string;

Also note the missing space after the colon on line 12.


16-45: Sample data uses past dates.

The newsletter dates are from 2024, but the PR was created in November 2025. While this is acceptable for sample data, consider updating them to more recent dates for a production-ready feature.

apps/web/src/app/(main)/newsletters/pagetransition.tsx (1)

67-106: Consider performance optimization for link interception.

The component attaches click listeners to all links on every pathname change. With many links, this could impact performance.

Consider these optimizations:

  1. Use event delegation on a parent container instead of individual link listeners
  2. Memoize handleClick with useCallback
  3. Only re-attach listeners when absolutely necessary

Example with event delegation:

 useEffect(() => {
   createBlocks()
   reveal()

-  const links = document.querySelectorAll("a[href]")
-  const handleClick = (e: Event) => {
-    const target = e.currentTarget as HTMLAnchorElement
+  const handleClick = (e: Event) => {
+    const target = (e.target as HTMLElement).closest('a[href]') as HTMLAnchorElement
+    if (!target) return
+    
     const href = target.getAttribute("href")
     
     // ... rest of the logic
   }

-  links.forEach((l) => l.addEventListener("click", handleClick))
-  return () => links.forEach((l) => l.removeEventListener("click", handleClick))
+  document.addEventListener("click", handleClick)
+  return () => document.removeEventListener("click", handleClick)
 }, [pathname])
apps/web/src/app/(main)/newsletters/page.tsx (3)

22-27: Remove the no-op useEffect watching showSidebar.

This effect body does nothing and React already re-renders on showSidebar changes, so this hook just adds noise. You can safely delete it to simplify the component.


82-89: Make hasActiveFilters explicitly boolean.

Right now hasActiveFilters holds a string or "", relying on truthiness when used in JSX. For clarity and type safety, it’s better to coerce to a boolean:

-  const hasActiveFilters = searchQuery || selectedMonth || selectedYear;
+  const hasActiveFilters = Boolean(
+    searchQuery || selectedMonth || selectedYear
+  );

This avoids accidental type surprises if you later pass it around.


299-301: Use a stable key (e.g. post.slug) instead of the array index.

Using index as the React key can cause unnecessary remounts when filters/search change the ordering, and you already have a unique slug per post:

-                  filteredPosts.map((post, index) => (
+                  filteredPosts.map((post) => (
                     <div
-                      key={index}
+                      key={post.slug}

This keeps component identity stable across re-sorts and filter changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97e699a and 75733d5.

⛔ Files ignored due to path filters (3)
  • apps/web/src/assets/images/gsoc.png is excluded by !**/*.png
  • apps/web/src/assets/images/opensox.jpg is excluded by !**/*.jpg
  • apps/web/src/assets/images/photu.jpg is excluded by !**/*.jpg
📒 Files selected for processing (9)
  • apps/web/src/app/(main)/newsletters/[slug]/page.tsx (1 hunks)
  • apps/web/src/app/(main)/newsletters/page.tsx (1 hunks)
  • apps/web/src/app/(main)/newsletters/pagetransition.tsx (1 hunks)
  • apps/web/src/app/content/newsletters/getting-started-with-nextjs.md (1 hunks)
  • apps/web/src/app/content/newsletters/mastering-react-hooks.md (1 hunks)
  • apps/web/src/app/content/newsletters/understanding-typescript.md (1 hunks)
  • apps/web/src/components/non-pro-news/News.tsx (1 hunks)
  • apps/web/src/data/newsletters.ts (1 hunks)
  • apps/web/src/lib/newslettercontent.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/web/src/components/non-pro-news/News.tsx (1)
apps/web/src/components/icons/icons.tsx (1)
  • Terminal (1-5)
apps/web/src/app/(main)/newsletters/[slug]/page.tsx (3)
apps/web/src/data/newsletters.ts (1)
  • posts (16-45)
apps/web/src/lib/newslettercontent.ts (1)
  • getNewsletterContent (8-45)
apps/web/src/app/(main)/newsletters/pagetransition.tsx (1)
  • PageTransition (7-124)
apps/web/src/app/(main)/newsletters/page.tsx (6)
apps/web/src/store/useShowSidebar.ts (1)
  • useShowSidebar (10-16)
apps/web/src/hooks/useSubscription.ts (1)
  • useSubscription (11-77)
apps/web/src/data/newsletters.ts (1)
  • posts (16-45)
apps/web/src/components/non-pro-news/News.tsx (1)
  • News (5-42)
apps/web/src/components/ui/IconWrapper.tsx (1)
  • IconWrapper (10-22)
apps/web/src/app/(main)/newsletters/pagetransition.tsx (1)
  • PageTransition (7-124)
🪛 ast-grep (0.39.9)
apps/web/src/app/(main)/newsletters/[slug]/page.tsx

[warning] 78-78: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/app/(main)/newsletters/[slug]/page.tsx

[error] 79-79: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 LanguageTool
apps/web/src/app/content/newsletters/getting-started-with-nextjs.md

[grammar] ~47-~47: Use a hyphen to join words.
Context: ... and Routing Next.js uses a file-system based router. Simply create a file in th...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
apps/web/src/app/content/newsletters/getting-started-with-nextjs.md

31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (7)
apps/web/src/app/content/newsletters/mastering-react-hooks.md (1)

1-796: Comprehensive newsletter content.

This is a well-structured and thorough guide on React Hooks covering essential concepts, performance optimization, custom hooks, and best practices. The code examples are clear and the content aligns well with the newsletter feature requirements.

apps/web/src/lib/newslettercontent.ts (1)

8-45: Well-implemented content loader with proper security measures.

The function correctly validates the slug to prevent path traversal attacks and handles errors appropriately. Since the content is team-managed (as per PR objectives), the markdown-to-HTML conversion via remark is sufficient for this use case.

Note: If you plan to accept user-generated content in the future, consider adding HTML sanitization using a library like DOMPurify.

apps/web/src/app/content/newsletters/understanding-typescript.md (1)

1-513: High-quality TypeScript guide.

Comprehensive TypeScript documentation covering all essential concepts with clear examples. The content is well-organized and provides valuable information for developers.

apps/web/src/app/(main)/newsletters/[slug]/page.tsx (2)

9-17: Correct Next.js 15 async params handling.

The component correctly handles async params by awaiting them and implements proper 404 handling for missing posts.


77-81: Using dangerouslySetInnerHTML with team-managed content.

While static analysis tools flag dangerouslySetInnerHTML, this usage is acceptable because:

  • Content comes from team-managed markdown files (per PR objectives)
  • HTML is generated via remark, which provides basic safety
  • No user-generated content is involved

If you plan to accept content from untrusted sources in the future, consider adding HTML sanitization using DOMPurify.

apps/web/src/app/(main)/newsletters/pagetransition.tsx (1)

16-31: Smooth page transition implementation.

The block-based transition effect is well-implemented with proper initialization and cleanup. The staggered animation provides a polished user experience.

apps/web/src/components/non-pro-news/News.tsx (1)

1-1: Clarify intent regarding unused custom Terminal icon component.

While a custom Terminal icon exists at apps/web/src/components/icons/icons.tsx, it's completely unused across the codebase. The lucide-react Terminal has been standardized and is actively used in 5 files, including News.tsx. Additionally, the implementations differ: the custom Terminal has a hardcoded className="h-6 w-6", while News.tsx needs className="w-5 h-5".

Before suggesting reuse, verify whether:

  • The custom Terminal is legacy dead code that should be removed, or
  • A conscious decision was made to standardize on lucide-react

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 (5)
apps/web/src/app/content/newsletters/getting-started-with-nextjs.md (1)

47-47: Minor style suggestion: hyphenate compound adjective.

The phrase "file-system based router" could be "file-system-based router" for consistency with standard compound adjective formatting, though both forms are acceptable.

apps/web/src/app/(main)/newsletters/page.tsx (4)

31-36: Remove unnecessary useEffect.

This effect serves no purpose—the component already re-renders when showSidebar changes because it's referenced in the JSX. The effect body is empty and can be safely deleted.

Apply this diff:

-  // Sidebar auto-hide hamburger when open
-  useEffect(() => {
-    if (showSidebar) {
-      // nothing needed, this triggers rerender & hides hamburger
-    }
-  }, [showSidebar]);
-

100-168: Consider extracting repeated sidebar/hamburger logic.

The sidebar, mobile overlay, and hamburger button code is duplicated across three UI branches (loading, non-Pro, Pro). Extracting this to a shared component or render function would improve maintainability.

For example, create a layout wrapper:

function NewsletterLayout({ 
  children, 
  showSidebar, 
  setShowSidebar 
}: { 
  children: React.ReactNode;
  showSidebar: boolean;
  setShowSidebar: (value: boolean) => void;
}) {
  return (
    <div className="flex w-screen h-screen bg-[#0a0a0b] overflow-hidden">
      <aside className={`h-full z-50 ${!showSidebar && "hidden xl:block"}`}>
        <Sidebar />
      </aside>
      
      {showSidebar && (
        <div
          onClick={() => setShowSidebar(false)}
          className="fixed inset-0 bg-black/40 backdrop-blur-sm xl:hidden z-40"
        />
      )}
      
      {!showSidebar && (
        <button
          onClick={() => setShowSidebar(true)}
          className="xl:hidden fixed top-4 left-4 z-50 w-12 h-12 rounded-lg bg-[#1a1a1d] border border-white/10 flex items-center justify-center hover:bg-[#2a2a2d] transition-colors"
        >
          <Bars3Icon className="size-5 text-ox-purple" />
        </button>
      )}
      
      {children}
    </div>
  );
}

308-347: Use post.slug instead of index as key.

Using array indices as keys can cause issues with React's reconciliation, especially if the list order changes. Since each post has a unique slug, use that instead.

Apply this diff:

-                    <div
-                      key={index}
-                      className="flex mb-[50px] relative top-[-80px] max-[1279px]:top-[-60px] max-[1024px]:static max-[1024px]:flex-col max-[1024px]:mb-10 max-[768px]:flex-col max-[768px]:items-start max-[768px]:mt-[30px]"
-                    >
+                    <div
+                      key={post.slug}
+                      className="flex mb-[50px] relative top-[-80px] max-[1279px]:top-[-60px] max-[1024px]:static max-[1024px]:flex-col max-[1024px]:mb-10 max-[768px]:flex-col max-[768px]:items-start max-[768px]:mt-[30px]"
+                    >

236-288: Consider adding ARIA labels for better accessibility.

The search input and filter dropdowns lack explicit labels. While placeholders provide visual hints, ARIA labels improve screen reader experience.

Example additions:

                    <input
                      type="text"
                      placeholder="Search newsletters..."
+                     aria-label="Search newsletters"
                      value={searchQuery}
                  <select
                    value={selectedMonth}
+                   aria-label="Filter by month"
                    onChange={(e) => setSelectedMonth(e.target.value)}
                  <select
                    value={selectedYear}
+                   aria-label="Filter by year"
                    onChange={(e) => setSelectedYear(e.target.value)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75733d5 and a0a467d.

📒 Files selected for processing (4)
  • apps/web/package.json (1 hunks)
  • apps/web/src/app/(main)/newsletters/page.tsx (1 hunks)
  • apps/web/src/app/content/newsletters/getting-started-with-nextjs.md (1 hunks)
  • apps/web/src/app/docs/how-to-add-newsletters.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/src/app/docs/how-to-add-newsletters.md
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/app/(main)/newsletters/page.tsx (6)
apps/web/src/store/useShowSidebar.ts (1)
  • useShowSidebar (10-16)
apps/web/src/hooks/useSubscription.ts (1)
  • useSubscription (11-77)
apps/web/src/data/newsletters.ts (1)
  • posts (16-45)
apps/web/src/components/non-pro-news/News.tsx (1)
  • News (5-42)
apps/web/src/components/ui/IconWrapper.tsx (1)
  • IconWrapper (10-22)
apps/web/src/app/(main)/newsletters/pagetransition.tsx (1)
  • PageTransition (7-124)
🪛 LanguageTool
apps/web/src/app/content/newsletters/getting-started-with-nextjs.md

[grammar] ~47-~47: Use a hyphen to join words.
Context: ... and Routing Next.js uses a file-system based router. Simply create a file in th...

(QB_NEW_EN_HYPHEN)

🔇 Additional comments (3)
apps/web/src/app/(main)/newsletters/page.tsx (2)

19-22: LGTM! Date parsing correctly handles timezone issues.

The parsePostDate function properly parses "YYYY-MM-DD" strings as local dates, preventing the timezone-dependent off-by-one errors that were flagged in previous reviews.


38-89: LGTM! Filtering and sorting logic is well-implemented.

The memoized computations correctly use parsePostDate throughout for consistent date handling, and the spread operator before sorting (line 86) properly avoids mutation.

apps/web/package.json (1)

30-30: GSAP version 3.13.0 is confirmed secure and current.

Verification shows GSAP 3.13.0 is the latest stable version, and while a HIGH severity prototype pollution vulnerability existed in versions < 3.6.0 (published 2021), it has been patched since version 3.6.0. The specified version is well above that threshold and safe to use.

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 (3)
apps/web/src/components/dashboard/Sidebar.tsx (3)

114-151: Route mapping looks good; consider reducing Pro CTA duplication

The sidebar body refactor to map SIDEBAR_ROUTES into <Link><SidebarItem/></Link> is clean and keeps nav items declarative.

For the Pro CTA block:

  • The custom <div> for !isCollapsed && !isPaidUser closely duplicates the structure and classes of SidebarItem, while SidebarItem itself already supports a badge prop (per SidebarItem.tsx).
  • You could simplify by always rendering a single SidebarItem and toggling the badge based on isPaidUser, which would remove duplicated layout/hover styling and keep everything going through one component.

For example:

-        {!isCollapsed && !isPaidUser ? (
-          <div
-            className="w-full h-[44px] flex items-center rounded-md cursor-pointer px-2 gap-3 pl-3 hover:bg-[#292929] group"
-            onClick={proClickHandler}
-          >
-            <span className="text-[#eaeaea] group-hover:text-white transition-colors">
-              <StarIcon className="size-5" />
-            </span>
-            <div className="flex items-center gap-1">
-              <h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-white transition-colors">
-                Opensox Pro
-              </h1>
-              <OpensoxProBadge className="px-1.5 py-0.5 scale-75" />
-            </div>
-          </div>
-        ) : (
-          <SidebarItem
-            itemName="Opensox Pro"
-            onclick={proClickHandler}
-            icon={<StarIcon className="size-5" />}
-            collapsed={isCollapsed}
-          />
-        )}
+        <SidebarItem
+          itemName="Opensox Pro"
+          onclick={proClickHandler}
+          icon={<StarIcon className="size-5" />}
+          collapsed={isCollapsed}
+          badge={
+            !isPaidUser ? (
+              <OpensoxProBadge className="px-1.5 py-0.5 scale-75" />
+            ) : null
+          }
+        />

Functionally this keeps Pro prominent for non‑paid users while simplifying the markup.


168-176: Harden click‑outside handler against non‑HTMLElement targets

The click‑outside logic is correct in spirit and attaches/removes the listener only when open is true, which is good. To make it more robust against rare cases where event.target isn’t an HTMLElement (e.g., certain text nodes), you can guard the closest call:

   React.useEffect(() => {
-    const handler = (e: MouseEvent) => {
-      if (open && !(e.target as HTMLElement).closest(".profile-menu-container")) {
-        setOpen(false);
-      }
-    };
+    const handler = (e: MouseEvent) => {
+      if (!open) return;
+      const target = e.target;
+      if (!(target instanceof HTMLElement)) return;
+
+      if (!target.closest(".profile-menu-container")) {
+        setOpen(false);
+      }
+    };
     if (open) document.addEventListener("mousedown", handler);
     return () => document.removeEventListener("mousedown", handler);
   }, [open]);

This keeps behavior the same while avoiding potential runtime issues if a non‑element target ever occurs.


179-250: Profile dropdown UX is solid; consider minor semantics/a11y tweaks

The profile card + dropdown look well‑structured:

  • profile-menu-container + absolute dropdown positioning give a predictable anchor.
  • AnimatePresence + motion.div provide clean enter/exit animations.
  • Using the same ProfilePic, fullName, and userEmail in both the trigger and dropdown header keeps identity clear.
  • Logout now uses ArrowRightOnRectangleIcon, which reads nicely in the menu.

Two minor, non‑blocking suggestions:

  1. Trigger semantics
    The clickable profile trigger is a div with onClick. For better accessibility and keyboard interaction, consider making it a <button type="button"> with appropriate aria-expanded and aria-haspopup="menu" attributes.

  2. Buttons inside the dropdown
    The “Account Settings” and “Logout” actions are correctly implemented as <button> elements. You might optionally add type="button" to make their intent explicit, especially if this component is ever used inside a <form>.

These are polish items; behavior as written is fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0a467d and bf2f3a1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/web/package.json (2 hunks)
  • apps/web/src/components/dashboard/Sidebar.tsx (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/package.json
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/components/dashboard/Sidebar.tsx (3)
apps/web/src/components/ui/IconWrapper.tsx (1)
  • IconWrapper (10-22)
apps/web/src/components/sidebar/SidebarItem.tsx (1)
  • SidebarItem (13-36)
apps/web/src/components/dashboard/ProfilePic.tsx (1)
  • ProfilePic (11-59)
🔇 Additional comments (3)
apps/web/src/components/dashboard/Sidebar.tsx (3)

9-28: Icon imports and library mix look consistent

Importing NewspaperIcon and ArrowRightOnRectangleIcon from @heroicons/react/24/outline and chevrons from lucide-react is fine and keeps icons grouped logically. Just be mindful of visual consistency between the two icon sets; if you see mismatched stroke weights in the UI, it may be worth standardizing on one library for header/profile chevrons.


29-50: Newsletters route wiring and Pro routing logic

The new SIDEBAR_ROUTES entry for path: "/newsletters" with NewspaperIcon is wired cleanly and will automatically participate in the sidebar mapping. The simplified proClickHandler ternary is also clearer than branching logic.

Two things to double‑check:

  • That the newsletters listing page actually lives at /newsletters (and uses the same dashboard shell), since other items are under /dashboard/....
  • That /dashboard/pro/dashboard is still the correct Pro destination; if this route ever changes, this constant will be easy to miss.

Otherwise, this config looks solid.

Also applies to: 61-63


80-112: Mobile and desktop headers are cleaner and more consistent

The revised mobile and desktop headers (brand link + collapse toggle with lucide chevrons) are straightforward and visually consistent:

  • Using <Link href="/"> for the brand on both breakpoints is good for UX.
  • The collapse toggle logic is easy to follow and the IconWrapper usage keeps hit‑area and styling consistent.
  • Background/border classes (bg-ox-sidebar, border-ox-header) align with the rest of the shell.

No functional issues from what I can see.

@apsinghdev
Copy link
Owner

@HarshJS30 thanks for the submission! Unfortunately, we are moving with a different submission this time, so we won't be able to accept it. Still, you are welcome to make contributions! 🙏

@apsinghdev apsinghdev closed this Nov 20, 2025
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.

[bounty-to-hire]: create ui for newsletter

2 participants