Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.

wip: added ui#186

Open
mosco98 wants to merge 12 commits into
mainfrom
feat-user-dashboard
Open

wip: added ui#186
mosco98 wants to merge 12 commits into
mainfrom
feat-user-dashboard

Conversation

@mosco98

@mosco98 mosco98 commented Mar 25, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Introduced a refreshed account layout featuring a fixed bottom navigation bar with dynamic active state highlighting.
    • Launched an enhanced account page presenting saved articles and highlights through a modern, responsive grid design with interactive buttons.
    • Added an improved following interface that enables easy toggling between topics and authors, complete with interactive dropdowns and visually engaging cards.
    • Released a new component for viewing articles in either grid or list format, with a progress indicator for each article.
    • Implemented a new mechanism for tracking and updating reading progress for articles.
    • Enhanced user authentication handling in the navigation component, providing visual feedback based on login status.
  • Bug Fixes

    • Enhanced error handling for data retrieval processes across various components.
  • Chores

    • Removed the bookmarks component to streamline user navigation and functionality.

@vercel

vercel Bot commented Mar 25, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
research ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 2, 2025 2:22pm

@coderabbitai

coderabbitai Bot commented Mar 25, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

The changes introduce four new Svelte components for the account section, along with modifications to existing components. A layout component renders a fixed bottom navigation bar with dynamic active state management. The account page component displays a welcome message, saved articles, and highlights using a CSS grid. Additionally, a following page component provides a toggle interface between followed topics and authors, featuring a dropdown mechanism and card-based display for each category. Lastly, a continue reading component allows users to view articles in either a grid or list format.

Changes

File(s) Change Summary
src/routes/account/+layout.svelte Introduced a new layout component using Svelte and TypeScript. Renders children and a fixed bottom navigation bar that iterates over a links array, dynamically updating the active state based on the current URL.
src/routes/account/+page.svelte Added a new account page component that displays a welcome message, saved articles, and highlights in a responsive grid layout.
src/routes/account/following/+page.svelte Introduced a new component for managing followed topics and authors, allowing users to toggle between these categories with a dropdown and display cards for each.
src/routes/account/continue-reading/+page.svelte Added a new component that allows users to view articles in either a grid or list format, with a toggle for view style and a responsive layout for displaying article details.
src/lib/components/ui/PopularReads.svelte Modified the getPrimaryCategory function to improve logic for determining the primary category of an article, ensuring a valid category is always returned.
src/lib/components/ui/ArticleBottomBar.svelte Added a conditional check in the handleScroll function to execute only if the isMobile variable is true, altering the control flow for scroll handling logic.
src/routes/[slug]/+page.svelte Introduced a derived store slug for tracking article slugs and modified the updateReadingProgress function to include a timeout mechanism for updating reading progress in the database only for authenticated users.
src/routes/bookmarks/+page.svelte Deleted the file containing a component responsible for displaying user bookmarks, including logic for fetching and rendering bookmarks from a Supabase database.
src/lib/components/ui/Nav.svelte Added user authentication handling, including fetching user info from Supabase and conditionally rendering UI elements based on login status.
src/lib/services/ghost.service.ts Updated the fetchGhostArticles function to include a new optional parameter userCache, allowing callers to bypass cache retrieval if desired.

Poem

I'm a hopping bunny on a coding spree,
New layouts and pages make my heart feel free.
Navigation, articles, and toggles all in a row,
Each commit makes my little tail glow.
In the digital garden, carrots pave the way—
CodeRabbit cheer, hip hip hooray!
🐇🥕

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/routes/[slug]/+page.svelte

Oops! Something went wrong! :(

ESLint: 9.12.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@eslint/js' imported from /eslint.config.js
at packageResolve (node:internal/modules/esm/resolve:839:9)
at moduleResolve (node:internal/modules/esm/resolve:908:18)
at defaultResolve (node:internal/modules/esm/resolve:1038:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
at ModuleJob._link (node:internal/modules/esm/module_job:126:49)


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 968092f and bb739bc.

⛔ Files ignored due to path filters (1)
  • static/account-header-bg.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • src/routes/account/+layout.svelte (1 hunks)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/following/+page.svelte (1 hunks)
🔇 Additional comments (4)
src/routes/account/+layout.svelte (2)

1-20: Well-structured navigation setup

The implementation of the navigation links using a structured array with labels, icons, and hrefs is clean and maintainable. This approach makes it easy to add or modify navigation items in the future.


28-38: LGTM: Active state handling

The active state detection and styling based on the current URL pathname is implemented correctly. This provides good visual feedback to users about their current location in the application.

src/routes/account/following/+page.svelte (2)

8-9: Use Svelte reactive declarations for derived state

The $state usage is correct, but consider using reactive declarations for values that can be derived from other state.


108-119: Add event handlers for card interactions

The card components are well-structured visually, but lack click handlers or links to navigate to the respective content. Consider adding navigation functionality to these cards.

Are these cards supposed to be clickable? If so, add appropriate navigation elements:

-	<div class="py-[31px] flex items-start justify-between">
+	<a href="/articles/some-article-slug" class="py-[31px] flex items-start justify-between hover:opacity-90 transition">

Also applies to: 156-206

Comment thread src/routes/account/+layout.svelte
Comment thread src/routes/account/following/+page.svelte
Comment thread src/routes/account/following/+page.svelte
Comment thread src/routes/account/following/+page.svelte
Comment thread src/routes/account/following/+page.svelte
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb739bc and d0cef62.

📒 Files selected for processing (2)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/continue-reading/+page.svelte (1 hunks)
🔇 Additional comments (5)
src/routes/account/+page.svelte (5)

6-6: Replace dummy data with proper data fetching

Using Array.from({ length: 5 }) creates placeholder data with undefined values. For a production-ready component, implement proper data fetching from an API or store.


15-17: Fix typo in image source path

There appears to be a typo in the image source filename.

-		src="/contritbutors-header.png"
+		src="/contributors-header.png"

36-47: Move "View All" button outside of the item loop

The "View All" button appears inside each item in the grid, which means it's being repeated for each article. This is likely not the intended design.


52-56: Add loading states and error handling for images

Images loaded from external sources might fail to load. Add appropriate error handling and loading states.

<img
  class="size-full object-cover group-hover:scale-110 transition will-change-transform"
  src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
  alt="thumbnail"
+ onerror="this.onerror=null; this.src='/placeholder.jpg';"
+ loading="lazy"
/>

150-311: Extract highlight card component to reduce duplication

There's significant code duplication in the highlight cards section. Consider extracting this into a reusable component to improve maintainability.

Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/continue-reading/+page.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/routes/account/+page.svelte (1)

179-180: 🧹 Nitpick (assertive)

Add responsive layout for saved articles.

The saved articles section uses a fixed grid column count without responsiveness. Consider adding responsive grid columns for better mobile experience.

-<div class="container">
-  <div class="grid grid-cols-5 border-l border-[#202020]">
+<div class="container">
+  <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 border-l border-[#202020]">
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0cef62 and 5ba8794.

📒 Files selected for processing (3)
  • src/lib/components/ui/PopularReads.svelte (1 hunks)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/continue-reading/+page.svelte (1 hunks)
🔇 Additional comments (16)
src/lib/components/ui/PopularReads.svelte (1)

34-37: Improved error handling for category selection.

This change enhances the getPrimaryCategory function to ensure it always returns a valid category. By storing the search result in a variable and providing a fallback to the first category when no primary category exists, the code now handles edge cases more effectively.

src/routes/account/continue-reading/+page.svelte (8)

8-8: Replace dummy data with proper data fetching.

Using Array.from({ length: 5 }) creates placeholder data with undefined values. For a production-ready component, implement proper data fetching from an API or store.

-const articles = Array.from({ length: 5 });
+import { onMount } from 'svelte';
+
+let articles = [];
+
+onMount(async () => {
+  try {
+    const response = await fetch('/api/continue-reading');
+    articles = await response.json();
+  } catch (error) {
+    console.error('Failed to fetch articles:', error);
+    // Implement proper error handling
+  }
+});

46-47: Fix incorrect aria-label for grid view button.

The aria-label for the grid view button incorrectly says "Switch to list view" but should say "Switch to grid view".

-  aria-label="Switch to list view"
+  aria-label="Switch to grid view"

64-69: Move "View All" button outside of the item loop.

The "View All" button appears inside each item in the grid, which means it's being repeated for each article. This is likely not the intended design.

<div class="grid grid-cols-3 gap-6">
+  <div class="col-span-3 flex justify-end mb-2">
+    <button
+      class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
+      >View All <ArrowRight
+        class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
+      /></button
+    >
+  </div>
  {#each articles as article}
    <div>
      <div class="flex items-center justify-between">
        <Badge class="rounded-none">Scaling</Badge>
-
-        <button
-          class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
-          >View All <ArrowRight
-            class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
-          /></button
-        >
      </div>
      <!-- Rest of the code -->

74-78: Add loading states and error handling for images.

Images loaded from external sources might fail to load. Add appropriate error handling and loading states.

<img
  class="size-full object-cover group-hover:scale-110 transition will-change-transform"
  src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
  alt="thumbnail"
+ loading="lazy"
+ onerror="this.onerror=null; this.src='/placeholder.jpg';"
/>

103-103: Fix line height property value.

The leading-0 property appears incorrect. In Tailwind CSS, line heights are typically specified as leading-none, leading-tight, leading-normal, etc., or with specific values like leading-4.

-<p class="w-20 text-right leading-0">40% Complete</p>
+<p class="w-20 text-right leading-none">40% Complete</p>

115-120: Fix group hover effect in list view.

The group-hover:scale-110 class is applied to the image, but no parent element has the group class in this context, so the hover effect won't work.

-<div class="w-[180px] h-[118.636px] relative">
+<div class="w-[180px] h-[118.636px] relative group">
  <img
    class="size-full object-cover group-hover:scale-110 transition will-change-transform"
    src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
    alt="thumbnail"
  />
</div>

150-150: Fix line height property value in list view.

The leading-0 property appears incorrect here as well.

-<p class="w-20 text-right leading-0">40% Complete</p>
+<p class="w-20 text-right leading-none">40% Complete</p>

1-160: Extract article card component to reduce duplication.

There's significant code duplication between the grid and list views. Consider extracting the article display logic into a reusable component.

Create a new component ArticleCard.svelte:

<script lang="ts">
  import { Badge } from '$lib/components/ui/badge';
  import { cn } from '$lib/utils/ui-components';
  
  export let viewStyle: 'GRID' | 'LIST';
  export let title: string;
  export let date: string;
  export let readTime: string;
  export let imageUrl: string;
  export let category: string;
  export let progress: number;
</script>

{#if viewStyle === 'GRID'}
  <div>
    <div class="flex items-center justify-between">
      <Badge>{category}</Badge>
    </div>

    <div class="mt-1 group">
      <div class="h-[165px] relative overflow-hidden cursor-pointer">
        <img
          class="size-full object-cover group-hover:scale-110 transition will-change-transform"
          src={imageUrl}
          alt="thumbnail"
          loading="lazy"
          onerror="this.onerror=null; this.src='/placeholder.jpg';"
        />
      </div>

      <div class="mt-4">
        <h3
          class="font-powerGroteskBold text-lg leading-[18.9px] group-hover:underline underline-offset-[3px]"
        >
          {title}
        </h3>

        <div
          class="mt-2 text-xs font-mono divide-x divide-[#333] text-neutral-40 flex items-center gap-2"
        >
          <p>{date}</p>
          <p class="pl-2">{readTime}</p>
        </div>
      </div>

      <div
        class="mt-3 text-xs font-mono text-neutral-10 flex items-center justify-between gap-10"
      >
        <div class="bg-[#272728] h-2.5 flex-1 rounded-[40px] relative overflow-hidden">
          <div class="bg-[#0AB2BA] absolute inset-y-0 w-1/2 rounded-[40px]" style="width: {progress}%"></div>
        </div>

        <p class="w-20 text-right leading-none">{progress}% Complete</p>
      </div>
    </div>
  </div>
{:else}
  <div class="flex items-center gap-6">
    <div class="w-[180px] h-[118.636px] relative group">
      <img
        class="size-full object-cover group-hover:scale-110 transition will-change-transform"
        src={imageUrl}
        alt="thumbnail"
        loading="lazy"
        onerror="this.onerror=null; this.src='/placeholder.jpg';"
      />
    </div>

    <div class="group space-y-2.5 flex-1">
      <Badge class="bg-white rounded-[2px] text-neutral-80 px-1.5 py-1 h-[22px]">{category}</Badge>

      <h3
        class="font-powerGroteskBold text-lg leading-[18.9px] group-hover:underline underline-offset-[3px] max-w-[782px]"
      >
        {title}
      </h3>

      <div
        class="text-xs font-mono divide-x divide-[#333] text-neutral-40 flex items-center gap-2"
      >
        <p>{date}</p>
        <p class="pl-2">{readTime}</p>
      </div>

      <div
        class="text-xs font-mono text-neutral-10 flex items-center justify-between gap-10"
      >
        <div
          class="bg-[#272728] h-2.5 flex-1 rounded-[40px] relative overflow-hidden max-w-[782px]"
        >
          <div class="bg-[#0AB2BA] absolute inset-y-0 w-1/2 rounded-[40px]" style="width: {progress}%"></div>
        </div>

        <p class="w-20 text-right leading-none">{progress}% Complete</p>
      </div>
    </div>
  </div>
{/if}
src/routes/account/+page.svelte (7)

94-97: Fix typo in image source path.

There appears to be a typo in the image source filename.

-src="/contritbutors-header.png"
+src="/contributors-header.png"

121-126: Move "View All" button outside of the item loop.

The "View All" button appears inside each item in the grid, which means it's being repeated for each article. This is likely not the intended design.

<div class="mt-7 grid grid-cols-3 gap-6">
+  <div class="col-span-3 flex justify-end mb-2">
+    <button
+      class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
+      >View All <ArrowRight
+        class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
+      /></button
+    >
+  </div>
  {#each Array.from({ length: 3 }) as article}
    <div>
      <div class="flex items-center justify-between">
        <Badge class="rounded-none">Scaling</Badge>
-
-        <button
-          class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
-          >View All <ArrowRight
-            class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
-          /></button
-        >
      </div>
      <!-- Rest of the code -->

131-135: Add loading states and error handling for images.

Images loaded from external sources might fail to load. Add appropriate error handling and loading states.

<img
  class="size-full object-cover group-hover:scale-110 transition will-change-transform"
  src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
  alt="thumbnail"
+ loading="lazy"
+ onerror="this.onerror=null; this.src='/placeholder.jpg';"
/>

160-160: Fix line height property value.

The leading-0 property appears incorrect. In Tailwind CSS, line heights are typically specified as leading-none, leading-tight, leading-normal, etc., or with specific values like leading-4.

-<p class="w-20 text-right leading-0">40% Complete</p>
+<p class="w-20 text-right leading-none">40% Complete</p>

171-175: Add link to "Saved Articles" section.

The "Continue Reading" section has a link (<a href>) that navigates to its dedicated page, but the "Saved Articles" section uses a <button> without navigation. For consistency, consider making this a link to a dedicated saved articles page.

-<button
-  class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-  >Saved Articles
-  <!-- <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" /> -->
-</button>
+<a href="/account/saved-articles" class="inline-flex items-center gap-4 group">
+  <button
+    class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+    >Saved Articles
+    <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+  </button>
+  <p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+    View all
+  </p>
+</a>

234-238: Add link to "Highlights" section.

Similar to the "Saved Articles" section, consider making the "Highlights" button a link to a dedicated highlights page for consistency with the "Continue Reading" section.

-<button
-  class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-  >Highlights
-  <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
-</button>
+<a href="/account/highlights" class="inline-flex items-center gap-4 group">
+  <button
+    class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+    >Highlights
+    <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+  </button>
+  <p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+    View all
+  </p>
+</a>

241-402: Extract highlight card component to reduce duplication.

There's significant code duplication in the highlight cards section. Consider extracting this into a reusable component to improve maintainability.

Create a new component HighlightCard.svelte:

<script lang="ts">
  export let title: string;
  export let author: string;
  export let content: string;
  export let imageUrl: string;
</script>

<div class="bg-[#161617] pt-4 px-5 pb-5 rounded-[8px] group">
  <div class="flex items-center gap-[14px]">
    <div class="size-14 rounded-[8px] flex-shrink-0 relative overflow-hidden">
      <img
        class="size-full object-cover group-hover:scale-110 transition will-change-transform"
        src={imageUrl}
        alt="thumbnail"
      />
    </div>

    <div>
      <h3 class="font-powerGroteskBold leading-[17.6px]">
        {title}
      </h3>

      <p class="text-xs text-neutral-40 font-mono mt-[2px]">by {author}</p>
    </div>
  </div>

  <p class="mt-4 text-sm text-neutral-10">
    {content}
  </p>
</div>

Comment thread src/routes/account/continue-reading/+page.svelte
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (2)
src/routes/[slug]/+page.svelte (2)

118-169: 🧹 Nitpick (assertive)

Optimize multiple database queries for reading progress.

Within the updateReadingProgress function, each ReadHistory record is fetched and updated with separate queries. If the user scrolls frequently, these calls could stack up. Consider updating progress less often or batching updates to reduce load on your database.


64-64: 🧹 Nitpick (assertive)

Typographical fix – 'occured' → 'occurred'.

Minor spelling mistake in the error message could be corrected for clarity.

- throw error(500, 'An error occured');
+ throw error(500, 'An error occurred');
♻️ Duplicate comments (14)
src/routes/account/continue-reading/+page.svelte (2)

120-123: ⚠️ Potential issue

Mismatch in aria-label for the toggle button.

The second toggle button calls viewStyle = 'GRID' but has the same aria-label text as the first. For accessibility, change it to "Switch to grid view."

- aria-label="Switch to list view"
+ aria-label="Switch to grid view"

80-82: 🧹 Nitpick (assertive)

Consider consistent navigation logic.

Using history.back() may behave unexpectedly if the user arrived from a different route. For consistency with line 57, use SvelteKit's goto to direct users to a known fallback (e.g., /account).

- const handleBackNavigate = () => {
-   history.back();
- };
+ const handleBackNavigate = () => {
+   goto('/account');
+ };
src/routes/account/+page.svelte (12)

104-108: 🧹 Nitpick (assertive)

Extract shared utility function for code reuse.

This getPrimaryCategory function appears to be duplicated across components according to previous review comments. It should be extracted to a shared utility file.

-function getPrimaryCategory(article: ArticleMetadata) {
-  const primary = article.categories.find((category) => category.is_primary);
-
-  return primary ?? article.categories[0];
-}
+import { getPrimaryCategory } from '$lib/utils/article-helpers';

129-132: ⚠️ Potential issue

Fix typo in image source path.

There's a typo in the image source filename.

-src="/contritbutors-header.png"
+src="/contributors-header.png"

156-176: 🛠️ Refactor suggestion

Move "View All" button outside of the loop.

The "View All" button appears inside each article in the grid, causing it to be repeated multiple times. It should be outside the loop for better UX.

<div class="mt-7 grid grid-cols-3 gap-6">
+  <div class="col-span-3 flex justify-end mb-2">
+    <a
+      href={`/category`}
+      data-sveltekit-preload-data
+    >
+      <button
+        class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
+        >View All <ArrowRight
+          class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
+        /></button
+      >
+    </a>
+  </div>
  {#each historyArticles as article}
    {@const primaryCategory = getPrimaryCategory(article)?.name}
    {@const formattedDate = format(article.created_at, 'dd MMM yyyy')}
    {@const progress = Math.floor(Number(article.progress))}

    <div>
      <div class="flex items-center justify-between">
        <a
          href={`/category/${formatCategorySlug(primaryCategory)}`}
          data-sveltekit-preload-data
        >
          <Badge class="rounded-none">{primaryCategory}</Badge></a
        >
-
-        <a
-          href={`/category/${formatCategorySlug(primaryCategory)}`}
-          data-sveltekit-preload-data
-        >
-          <button
-            class="flex items-center gap-1 text-xs !font-mono text-neutral-20 hover:text-neutral-40 group transition"
-            >View All <ArrowRight
-              class="size-3.5 group-hover:translate-x-[2px] transition will-change-transform"
-            /></button
-          ></a
-        >
      </div>

214-216: 🧹 Nitpick (assertive)

Fix line height property value.

The leading-0 property is incorrect. In Tailwind CSS, line heights should be specified using valid values.

-<p class="w-20 text-right leading-0">
+<p class="w-20 text-right leading-none">
  {progress}% Complete
</p>

227-232: 🧹 Nitpick (assertive)

Add link to "Saved Articles" section.

For consistency with the "Continue Reading" section, make this a link to a dedicated saved articles page.

-<button
-  class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-  >Saved Articles
-  <!-- <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" /> -->
-</button>
+<a href="/account/saved-articles" class="inline-flex items-center gap-4 group">
+  <button
+    class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+    >Saved Articles
+    <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+  </button>
+  <p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+    View all
+  </p>
+</a>

237-284: 🧹 Nitpick (assertive)

Add responsive layout for saved articles grid.

The saved articles section uses a fixed 5-column grid which won't work well on smaller screens.

-<div class="grid grid-cols-5 border-l border-[#202020]">
+<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 border-l border-[#202020]">

243-246: 🧹 Nitpick (assertive)

Remove commented-out code.

Commented-out code should be removed as it creates confusion and clutters the file.

<div
  class={cn(
    'px-5 pt-[31px] pb-[50px] border-r border-[#202020]'
-    // i === 0 && 'border-l border-[#202020]',
-    // savedArticles.length - 1 === i && '!border-r border-[#202020]'
  )}
>

291-295: 🧹 Nitpick (assertive)

Add link to "Highlights" section.

For consistency with other sections, make the highlights button a link to a dedicated page.

-<button
-  class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-  >Highlights
-  <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
-</button>
+<a href="/account/highlights" class="inline-flex items-center gap-4 group">
+  <button
+    class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+    >Highlights
+    <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+  </button>
+  <p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+    View all
+  </p>
+</a>

298-459: 🛠️ Refactor suggestion

Extract highlight card component to reduce duplication.

There's significant code duplication in the highlight cards section. Consider extracting this into a reusable component.

Create a new HighlightCard.svelte component and use it for all highlight cards to improve maintainability.


302-306: 🧹 Nitpick (assertive)

Add loading states and error handling for images.

Images loaded from external sources might fail to load. Add appropriate error handling and loading states.

<img
  class="size-full object-cover group-hover:scale-110 transition will-change-transform"
  src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
  alt="thumbnail"
+ onerror="this.onerror=null; this.src='/placeholder.jpg';"
+ loading="lazy"
/>

298-459: 🛠️ Refactor suggestion

Replace hardcoded highlights data with proper data fetching.

The highlights section uses hardcoded data. For a production-ready component, implement proper data fetching from an API or store.

+ let highlights = $state<any[]>([]);
+
+ const getHighlights = async (userId: string) => {
+   try {
+     const { data, error: highlightsError } = await supabase
+       .from('UserHighlights')
+       .select('*')
+       .eq('userId', userId)
+       .order('created_at', { ascending: false });
+
+     if (data) {
+       highlights = data;
+     }
+
+     if (highlightsError) {
+       toast.error('Error fetching highlights');
+     }
+   } catch (err) {
+     console.error(err);
+     toast.error('An error occurred fetching highlights');
+   }
+ };
+
+ // Update handleDataFetch to include:
+ // await getHighlights(user.id);

Then in the template:

<div class="mt-7 grid grid-cols-3 gap-4">
  {#if highlights.length === 0}
    <div class="col-span-3 text-center py-10">
      <p class="text-lg text-neutral-40">You haven't highlighted any content yet.</p>
    </div>
  {:else}
    {#each highlights as highlight}
      <!-- Highlight card component -->
    {/each}
  {/if}
</div>

238-284: 🧹 Nitpick (assertive)

Add empty state handling for saved articles.

The component doesn't handle cases where savedArticles is empty, which could lead to an empty grid with no user feedback.

<div class="grid grid-cols-5 border-l border-[#202020]">
+  {#if savedArticles.length === 0}
+    <div class="col-span-5 text-center py-10">
+      <p class="text-lg text-neutral-40">You haven't saved any articles yet.</p>
+      <a href="/discover" class="text-[#0AB2BA] mt-2 inline-block hover:underline">Discover articles</a>
+    </div>
+  {:else}
    {#each savedArticles as article, i}
      <!-- Existing article rendering -->
    {/each}
+  {/if}
</div>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ba8794 and 3b52b62.

📒 Files selected for processing (5)
  • src/lib/components/ui/ArticleBottomBar.svelte (1 hunks)
  • src/routes/[slug]/+page.svelte (2 hunks)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/continue-reading/+page.svelte (1 hunks)
  • src/routes/bookmarks/+page.svelte (0 hunks)
💤 Files with no reviewable changes (1)
  • src/routes/bookmarks/+page.svelte
🔇 Additional comments (5)
src/lib/components/ui/ArticleBottomBar.svelte (1)

27-27: Prevent unnecessary scroll handling on desktop.

This newly added early return is a good optimization to avoid executing unnecessary logic on non-mobile devices.

src/routes/account/continue-reading/+page.svelte (4)

149-159: Repeated 'View All' button within the loop.

This button appears for each article, likely causing duplication and user confusion. Move the "View All" button outside the individual item loop to avoid repeated rendering.


198-200: Use valid Tailwind line-height values.

The class leading-0 is typically invalid. Tailwind uses leading-none, leading-tight, etc. This issue was flagged previously.

- <p class="w-20 text-right leading-0">
+ <p class="w-20 text-right leading-none">

218-219: Missing parent 'group' class for hover effect.

This image uses group-hover:scale-110 but is missing a .group class on a wrapping container, so the hover effect won’t trigger.

- <div class="w-[180px] h-[118.636px] relative">
+ <div class="w-[180px] h-[118.636px] relative group">

262-262: Use valid Tailwind line-height utilities.

Again, leading-0 is invalid. Replace with a valid Tailwind utility, e.g., leading-none.

Comment thread src/routes/account/continue-reading/+page.svelte
Comment thread src/routes/account/continue-reading/+page.svelte
Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/routes/account/+page.svelte (2)

125-125: 🛠️ Refactor suggestion

Use dynamic username instead of hardcoded welcome message

The welcome message should include the user's name for a personalized experience.

-<h1 class="text-[32px] font-powerGroteskBold">Welcome back</h1>
+<h1 class="text-[32px] font-powerGroteskBold">Welcome back, {user?.user_metadata?.full_name || 'there'}</h1>

214-216: ⚠️ Potential issue

Fix incorrect line height property

The leading-0 property is not a valid Tailwind class. Use a proper line height value.

-<p class="w-20 text-right leading-0">
+<p class="w-20 text-right leading-none">
	{progress}% Complete
</p>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b52b62 and b391831.

📒 Files selected for processing (4)
  • src/lib/components/ui/Nav.svelte (3 hunks)
  • src/lib/components/ui/PopularReads.svelte (2 hunks)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/continue-reading/+page.svelte (1 hunks)
🔇 Additional comments (28)
src/lib/components/ui/PopularReads.svelte (2)

35-37: Consider handling empty categories
If article.categories happens to be empty, accessing article.categories[0] may yield an undefined value and cause a runtime error. Please ensure that article.categories is non-empty or handle this scenario with a fallback.


60-62: Heading style update looks good
No concerns here. The new font class and text changes are clear and consistent.

src/lib/components/ui/Nav.svelte (2)

3-5: Import additions
These new imports (Supabase and Lucide icons) align well with subsequent usage.


57-83: Verify user metadata checks
While referencing user.user_metadata.email_verified, ensure user_metadata is always defined to prevent potential runtime errors if the structure is missing or null.

src/routes/account/continue-reading/+page.svelte (14)

1-15: Imports look fine
No issues noted with these import statements—they appear consistent with usage throughout this component.


16-17: Type definition is clear
Defining ArticleWithProgress is a straightforward way to track reading progress.


18-19: Appropriate state variables
Using $state for historyArticles and isLoading is a logical approach for maintaining component state.


21-42: Potential performance bottleneck
Calling getGhostArticleBySlug inside a loop can be slow for large histories. Consider batching or handling multiple slugs in a single fetch.


44-66: Correct the spelling
A minor spelling oversight: use "occurred" instead of "occured" in the error message.


68-72: Handle empty categories
As in similar logic elsewhere, calling article.categories[0] could fail if categories is empty. Consider a fallback or guard.


74-76: Data fetch on mount
Triggering handleDataFetch() within $effect is valid here; no further issues found.


78-82: Use predictable navigation
history.back() can be unreliable. Consider goto('/account') or another fixed route for a more stable user experience.


85-91: Loading overlay implementation
This loading state is straightforward and provides clear feedback during data retrieval.


122-123: Aria label mismatch
This button toggles to the grid view, but its label says "Switch to list view."


149-159: "View All" button duplication
The "View All" button appears for each item. If it's meant to show all articles, consider placing it outside the loop to avoid repetition.


198-200: Replace non-standard leading-0
Tailwind CSS doesn't provide leading-0. Use leading-none or an alternative valid utility.


217-219: Missing .group on parent
.group-hover:scale-110 won't apply without a .group class on the containing element.


262-262: Invalid line-height utility
leading-0 is not a recognized Tailwind class; redefine as leading-none or another appropriate value.

src/routes/account/+page.svelte (10)

104-108: Reuse the enhanced getPrimaryCategory function from PopularReads

This function implementation is identical to the one in PopularReads.svelte. Consider extracting this to a utility function to maintain DRY principles.

-function getPrimaryCategory(article: ArticleMetadata) {
-  const primary = article.categories.find((category) => category.is_primary);
-
-  return primary ?? article.categories[0];
-}
+import { getPrimaryCategory } from '$lib/utils/article-helpers';

129-132: Fix typo in image source path

There appears to be a typo in the image source filename.

-		src="/contritbutors-header.png"
+		src="/contributors-header.png"

150-222: Add empty state handling for history articles

The component doesn't handle cases where there are no recently read articles, which could lead to an empty view without user feedback.

<div class="mt-7 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+	{#if historyArticles.length === 0}
+		<div class="col-span-1 md:col-span-2 lg:col-span-3 text-center py-10">
+			<p class="text-lg text-neutral-40">You haven't read any articles yet.</p>
+			<a href="/discover" class="text-[#0AB2BA] mt-2 inline-block hover:underline">Discover articles</a>
+		</div>
+	{:else}
	{#each historyArticles as article}
		{@const primaryCategory = getPrimaryCategory(article)?.name}
		{@const formattedDate = format(article.created_at, 'dd MMM yyyy')}
		{@const progress = Math.floor(Number(article.progress))}

		<!-- Existing article rendering -->
	{/each}
+	{/if}
</div>

226-232: Add link to "Saved Articles" section

Similar to the "Continue Reading" section, the "Saved Articles" heading should be a link to a dedicated page.

-<button
-	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-	>Saved Articles
-	<!-- <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" /> -->
-</button>
+<a href="/account/saved-articles" class="inline-flex items-center gap-4 group">
+	<button
+		class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+		>Saved Articles
+		<ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+	</button>
+	<p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+		View all
+	</p>
+</a>

238-239: Add empty state handling for saved articles

The component doesn't handle cases where there are no saved articles, which could lead to an empty view without user feedback.

<div
	class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 border-l border-[#202020]"
>
+	{#if savedArticles.length === 0}
+		<div class="col-span-1 md:col-span-3 lg:col-span-4 xl:col-span-5 text-center py-10 px-5">
+			<p class="text-lg text-neutral-40">You haven't saved any articles yet.</p>
+			<a href="/discover" class="text-[#0AB2BA] mt-2 inline-block hover:underline">Discover articles</a>
+		</div>
+	{:else}
	{#each savedArticles as article, i}
		<!-- Existing code -->
	{/each}
+	{/if}

287-291: Add link to "Highlights" section

Similar to the "Saved Articles" section, consider making the "Highlights" button a link to a dedicated highlights page for consistency with the "Continue Reading" section.

-<button
-	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-	>Highlights
-	<ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
-</button>
+<a href="/account/highlights" class="inline-flex items-center gap-4 group">
+	<button
+		class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+		>Highlights
+		<ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+	</button>
+	<p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+		View all
+	</p>
+</a>

294-455: Extract highlight card component to reduce duplication

There's significant code duplication in the highlight cards section. Consider extracting this into a reusable component.

Create a new component HighlightCard.svelte:

<script lang="ts">
  export let title: string;
  export let author: string;
  export let content: string;
  export let imageUrl: string;
</script>

<div class="bg-[#161617] pt-4 px-5 pb-5 rounded-[8px] group">
  <div class="flex items-center gap-[14px]">
    <div class="size-14 rounded-[8px] flex-shrink-0 relative overflow-hidden">
      <img
        class="size-full object-cover group-hover:scale-110 transition will-change-transform"
        src={imageUrl}
        alt={`Thumbnail for article: ${title}`}
        loading="lazy"
      />
    </div>

    <div>
      <h3 class="font-powerGroteskBold leading-[17.6px]">
        {title}
      </h3>

      <p class="text-xs text-neutral-40 font-mono mt-[2px]">by {author}</p>
    </div>
  </div>

  <p class="mt-4 text-sm text-neutral-10">
    {content}
  </p>
</div>

Then replace the repeated highlight sections with:

<div class="mt-7 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <HighlightCard 
    title="DePIN On Ethereum: Redefining Coordination Systems"
    author="sm-stack"
    content="Also known as 'soft confirmations', the sequencer's confirmation of transaction success (or failure) are useful for time-sensitive users who want to avoid waiting for transactions to finalize on Ethereum L1 before knowing a transaction's status."
    imageUrl="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
  />
  
  <!-- Repeat for other highlight cards -->
</div>

39-52: Improve error handling for bookmarks

The current error handling has a specific case for 'PGRST116' (no results found) but could be improved to handle more specific error scenarios.

if (dataError) {
  if (dataError.code === 'PGRST116') {
    const { error: createError } = await supabase
      .from('UserBookmarks')
      .insert({ userId: id, articleIds: [] });

    if (createError) {
-     toast.error('An error occured. Please try again.');
+     console.error('Error creating bookmarks record:', createError);
+     toast.error('Could not initialize bookmarks. Please try again later.');
    }
+  } else if (dataError.code === 'PGRST104') {
+    toast.error('Database connection error. Please try again later.');
  } else {
-   toast.error('Error fetching bookmarks');
+   console.error('Error fetching bookmarks:', dataError);
+   toast.error('Could not load your bookmarks. Please try again later.');
    throw error(500, 'Error fetching bookmarks');
  }
}

17-19: Consider initializing loading state to false by default

Currently, isLoading is initialized to true, but it's immediately set to true again in handleDataFetch. This could cause UI flashing if there's any delay between component mount and the effect execution.

-let isLoading = $state(true);
+let isLoading = $state(false);

298-302: Add loading states and error handling for images

Images loaded from external sources might fail to load. Add appropriate error handling and loading states.

<img
	class="size-full object-cover group-hover:scale-110 transition will-change-transform"
	src="https://ghost-2077.arvensis.systems/content/images/2025/03/rsby3bojlx6dmvwsvt7a-1.webp"
	alt="thumbnail"
+	onerror="this.onerror=null; this.src='/placeholder.jpg';"
+	loading="lazy"
/>

Comment thread src/lib/components/ui/Nav.svelte
Comment thread src/routes/account/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
src/routes/account/continue-reading/+page.svelte (2)

125-126: ⚠️ Potential issue

Correct aria-label to reflect 'Switch to grid view'
When the button sets viewStyle = 'GRID', the aria-label should say "Switch to grid view" for accurate accessibility.

-					aria-label="Switch to list view"
+					aria-label="Switch to grid view"

65-65: 🧹 Nitpick (assertive)

Fix typo in error message
You have "An error occured" in the thrown error message. Please replace with "occurred" for correctness.

-			throw error(500, 'An error occured');
+			throw error(500, 'An error occurred');
src/routes/account/+page.svelte (1)

144-155: ⚠️ Potential issue

Avoid nesting button inside an anchor
Nesting a <button> inside an <a> is invalid HTML and may cause accessibility issues. Use one interactive element.

-<a href="/account/continue-reading" class="inline-flex items-center gap-4 group">
-	<button
-		class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
-	>
-		Continue Reading
-		<ChevronRight
-			class="size-5 group-hover:translate-x-[2px] transition will-change-transform"
-		/>
-	</button>
-	<p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
-		View all
-	</p>
-</a>
+<a
+  href="/account/continue-reading"
+  class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+>
+  Continue Reading
+  <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+</a>
+<p class="text-[#0AB2BA] text-sm font-mono opacity-0 group-hover:opacity-100 transition">
+  View all
+</p>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b391831 and 7f0d082.

📒 Files selected for processing (2)
  • src/routes/account/+page.svelte (1 hunks)
  • src/routes/account/continue-reading/+page.svelte (1 hunks)
🔇 Additional comments (6)
src/routes/account/continue-reading/+page.svelte (2)

221-221: Use valid Tailwind line-height utilities
The leading-0 class is not recognized by Tailwind. Consider using leading-none, leading-tight, or a similar valid utility class.

-	<p class="w-20 text-right leading-0">
+	<p class="w-20 text-right leading-none">

Also applies to: 265-265


156-162: Move "View All" button outside the loop
Placing "View All" inside each article block repeats it unnecessarily. It's best to place such a navigation button outside the loop.

src/routes/account/+page.svelte (4)

136-136: Fix filename typo
Replace "contritbutors-header.png" with "contributors-header.png" to match the correct spelling.

-		src="/contritbutors-header.png"
+		src="/contributors-header.png"

221-221: Use valid Tailwind line-height utility
leading-0 is not recognized and should be replaced with a valid utility like leading-none.

-								<p class="w-20 text-right leading-0">
+								<p class="w-20 text-right leading-none">

236-239: Use a link for "Saved Articles"
For consistency with the "Continue Reading" section, consider making this a link to a dedicated page.

-<button
-	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
->Saved Articles
+<a
+	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+	href="/account/saved-articles"
+>Saved Articles

295-298: Add a link for "Highlights"
Rather than a plain button, use a link for "Highlights" to match the pattern used elsewhere for navigation.

-<button
-	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
->Highlights
-<ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
-</button>
+<a
+	href="/account/highlights"
+	class="text-xl !font-powerGroteskBold inline-flex items-center gap-1.5 hover:text-neutral-20 transition group"
+>
+  Highlights
+  <ChevronRight class="size-5 group-hover:translate-x-[2px] transition will-change-transform" />
+</a>

Comment thread src/routes/account/continue-reading/+page.svelte Outdated
Comment thread src/routes/account/+page.svelte
Comment thread src/routes/account/+page.svelte

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (5)
src/lib/services/ghost.service.ts (5)

5-11: 🧹 Nitpick (assertive)

Consider documenting the userCache parameter purpose

While the implementation is correct, adding a JSDoc comment would make the function's behavior more explicit to other developers.

 export const fetchGhostArticles = async (
 	category?: string,
 	page = 1,
 	limit = 15,
 	bookmarks?: string[],
+	/** Controls whether to use cached data (true) or always fetch fresh data (false) */
 	userCache = true
 ) => {

12-19: 🛠️ Refactor suggestion

Potential issue with cache key not including bookmarks

The cache key is generated using only category, page, and limit, but not bookmarks. This could lead to incorrect cached data being returned if different bookmarks are provided with the same other parameters.

-	const cacheKey = `${category || 'all'}-${page || 1}-${limit}`;
+	const bookmarksKey = bookmarks?.length ? `-bookmarks:${bookmarks.join(',')}` : '';
+	const cacheKey = `${category || 'all'}-${page || 1}-${limit}${bookmarksKey}`;

26-27: ⚠️ Potential issue

Potential filter conflict with both category and bookmarks

When both category and bookmarks are provided, the current implementation might cause one filter to override the other since both are setting the same filter property in the API request object.

-	...(category && { filter: `tag:${category}` }),
-	...((bookmarks || []).length > 0 && { filter: `slug:[${bookmarks}]` })
+	filter: [
+		...(category ? [`tag:${category}`] : []),
+		...((bookmarks || []).length > 0 ? [`slug:[${bookmarks}]`] : [])
+	].join('+')

57-93: 🧹 Nitpick (assertive)

Consider applying the userCache parameter to fetchGhostArticlesFilters for consistency

For code consistency, consider adding the userCache parameter to the fetchGhostArticlesFilters function as well, following the same pattern implemented in fetchGhostArticles.

-export const fetchGhostArticlesFilters = async (categories: string[], page = 1, limit = 6) => {
+export const fetchGhostArticlesFilters = async (categories: string[], page = 1, limit = 6, userCache = true) => {
 	const cacheKey = `${categories.join(',') || 'all'}-${page || 1}-${limit}`;
 
-	if (cache.has(cacheKey)) {
+	if (userCache && cache.has(cacheKey)) {
 		const cached = cache.get(cacheKey);
 		if (cached && isCacheValid(cached.timestamp)) {
 			return cached.data;
 		}
 	}

38-38: 🧹 Nitpick (assertive)

Consider respecting userCache for cache setting too

Currently, the function still sets the cache even when userCache is false. Consider making the caching behavior consistent by also skipping the cache setting operation when userCache is false.

-	cache.set(cacheKey, { data: articles, timestamp: Date.now() });
+	if (userCache) {
+		cache.set(cacheKey, { data: articles, timestamp: Date.now() });
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f0d082 and 39654e7.

📒 Files selected for processing (2)
  • src/lib/services/ghost.service.ts (1 hunks)
  • src/routes/account/+page.svelte (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/lib/services/ghost.service.ts (1)
src/lib/utils/cache.ts (1)
  • cache (1-1)
🔇 Additional comments (8)
src/lib/services/ghost.service.ts (2)

9-10: New parameter enables cache control

The addition of the userCache parameter with a default value of true allows callers to bypass the cache when needed, which is a useful enhancement for scenarios where fresh data is required.


14-14: Cache check now respects the userCache parameter

Good implementation of the conditional cache check that only retrieves from cache when userCache is true.

src/routes/account/+page.svelte (6)

46-46: Correct the spelling of “occured.”
Consider changing “occured” to “occurred,” as previously noted.


109-109: Spelling fix needed
Replace "An error occured" with "An error occurred" to align with proper spelling.


147-157: Avoid embedding a within an element
This pattern violates HTML standards and can lead to accessibility issues. Use a single clickable element.


223-223: Use valid Tailwind line-height classes
leading-0 is likely incorrect. Replace with a supported class such as leading-none.


237-241: Convert “Saved Articles” button to a link
For consistent navigation, consider using an anchor tag instead of a button here.


302-464: Extract highlight card code into a reusable component
Repeating the same markup for each highlight is prone to duplication. Extracting a HighlightCard component would simplify maintenance.

Comment thread src/routes/account/+page.svelte

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
src/routes/[slug]/+page.svelte (1)

118-169: 🧹 Nitpick (assertive)

Improve reading progress tracking with database persistence.

The updated implementation adds proper debouncing with timeout management and database persistence for reading progress. This ensures that reading position is saved only when needed (after 2 seconds of inactivity) and only for logged-in users.

A few observations:

  • Good error handling for when the record doesn't exist (PGRST116 error code)
  • Proper cleanup of timeouts to prevent memory leaks

Consider extracting the database operations into a separate function to improve readability:

async function updateReadingProgress() {
  const article = document.querySelector('article');
  if (!article) return;

  const windowHeight = window.innerHeight;
  const articleHeight = article.offsetHeight;
  const scrollTop = window.scrollY;

  const totalHeight = articleHeight - windowHeight;
  const currentProgress = (scrollTop / totalHeight) * 100;
  progress = Math.min(Math.max(currentProgress, 0), 100);

  if (isLoggedIn) {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }

    timeoutId = setTimeout(async () => {
-     const { data, error } = await supabase
-       .from('ReadHistory')
-       .select('*')
-       .eq('userId', userId)
-       .eq('articleId', $slug)
-       .limit(1)
-       .single();
-
-     if (data) {
-       const { error } = await supabase
-         .from('ReadHistory')
-         .update({ progress })
-         .eq('userId', userId)
-         .eq('articleId', $slug)
-         .select();
-
-       if (error) {
-         console.error('Failed to update progress:', error);
-       }
-     }
-
-     if (error) {
-       if (error.code === 'PGRST116') {
-         const { error: createError } = await supabase
-           .from('ReadHistory')
-           .insert({ userId, articleId: $slug, progress });
-
-         if (createError) {
-           console.error('Failed to update progress:', error);
-         }
-       }
-     }
+     await saveReadingProgress(userId, $slug, progress);
    }, 2000);
  }
}

+ async function saveReadingProgress(userId: string, articleId: string, progress: number) {
+   const { data, error } = await supabase
+     .from('ReadHistory')
+     .select('*')
+     .eq('userId', userId)
+     .eq('articleId', articleId)
+     .limit(1)
+     .single();
+
+   if (data) {
+     const { error } = await supabase
+       .from('ReadHistory')
+       .update({ progress })
+       .eq('userId', userId)
+       .eq('articleId', articleId)
+       .select();
+
+     if (error) {
+       console.error('Failed to update progress:', error);
+     }
+   }
+
+   if (error) {
+     if (error.code === 'PGRST116') {
+       const { error: createError } = await supabase
+         .from('ReadHistory')
+         .insert({ userId, articleId, progress });
+
+       if (createError) {
+         console.error('Failed to update progress:', error);
+       }
+     }
+   }
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57cd9c8 and 1cba832.

📒 Files selected for processing (1)
  • src/routes/[slug]/+page.svelte (6 hunks)
🔇 Additional comments (6)
src/routes/[slug]/+page.svelte (6)

115-116: Good addition of a derived store for the slug.

Using a derived store to extract the article slug from the URL is a good practice, making the slug reactive and available throughout the component.


838-845: Good state management for the new highlight feature.

The state variables for the highlighting feature are well-defined using Svelte's reactive state system.


846-857: Well-implemented selection check function.

The hasActiveSelection function properly checks if there is valid text selected within the article element.


891-904: Good utility function to get selection offsets.

This helper function cleanly extracts the start and end offsets of the current selection, which will be useful for storing highlights.


931-945: Good event listener management with proper cleanup.

The effect correctly adds and removes event listeners when the component mounts and unmounts, which prevents memory leaks.


1523-1553: Good binding of the article element for the highlighting feature.

The bind:this={articleRef} properly connects the article DOM element to the variable used for checking text selections. This is an essential part of making the highlighting feature work correctly.

Comment on lines +1193 to +1204
<!-- Hightlight menu -->
{#if menuVisible}
<div
class="absolute rounded-2xl border border-[#333] bg-[#19191b] z-[9999] text-sm"
style="top: {menuY}px; left: {menuX}px;"
>
<button class="!font-mono p-1" onclick={highlightText}>Highlight</button>
<!-- <button>Respond</button>
<button>Share</button>
<button>Private note</button> -->
</div>
{/if}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Implement the remaining highlighting UI functionality.

The highlighting menu is implemented but only has a single functional button. The other buttons are commented out, suggesting incomplete functionality.

Consider implementing at least some of the basic functionality for these buttons, or removing the commented code if they're not planned for the current release:

<!-- Hightlight menu -->
{#if menuVisible}
  <div
    class="absolute rounded-2xl border border-[#333] bg-[#19191b] z-[9999] text-sm"
    style="top: {menuY}px; left: {menuX}px;"
  >
    <button class="!font-mono p-1" onclick={highlightText}>Highlight</button>
-   <!-- <button>Respond</button>
-   <button>Share</button>
-   <button>Private note</button> -->
+   <button class="!font-mono p-1" onclick={() => {
+     const text = encodeURIComponent(selectionText);
+     const url = `https://twitter.com/intent/tweet?text="${text}" - from article: ${encodedUrl}`;
+     window.open(url, '_blank');
+     menuVisible = false;
+   }}>Share</button>
  </div>
{/if}
📝 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
<!-- Hightlight menu -->
{#if menuVisible}
<div
class="absolute rounded-2xl border border-[#333] bg-[#19191b] z-[9999] text-sm"
style="top: {menuY}px; left: {menuX}px;"
>
<button class="!font-mono p-1" onclick={highlightText}>Highlight</button>
<!-- <button>Respond</button>
<button>Share</button>
<button>Private note</button> -->
</div>
{/if}
<!-- Hightlight menu -->
{#if menuVisible}
<div
class="absolute rounded-2xl border border-[#333] bg-[#19191b] z-[9999] text-sm"
style="top: {menuY}px; left: {menuX}px;"
>
<button class="!font-mono p-1" onclick={highlightText}>Highlight</button>
<button class="!font-mono p-1" onclick={() => {
const text = encodeURIComponent(selectionText);
const url = `https://twitter.com/intent/tweet?text="${text}" - from article: ${encodedUrl}`;
window.open(url, '_blank');
menuVisible = false;
}}>Share</button>
</div>
{/if}

Comment on lines +917 to +929
const handleClickOutsideSelection = (event: MouseEvent): void => {
const target = event.target as HTMLElement;

const active = hasActiveSelection();

if (
!articleRef ||
(!target.closest('.menu') && !articleRef.contains(target)) ||
(menuVisible && target.closest('.menu') && articleRef.contains(target))
) {
menuVisible = false;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Improve the click outside detection logic.

The current implementation has complex conditions that could be simplified. Consider using a clearer approach for determining when to hide the menu.

const handleClickOutsideSelection = (event: MouseEvent): void => {
  const target = event.target as HTMLElement;
  const active = hasActiveSelection();

- if (
-   !articleRef ||
-   (!target.closest('.menu') && !articleRef.contains(target)) ||
-   (menuVisible && target.closest('.menu') && articleRef.contains(target))
- ) {
-   menuVisible = false;
- }
+ // Hide menu if we click outside the menu and selection is no longer active
+ if (menuVisible && !target.closest('.menu') && !active) {
+   menuVisible = false;
+ }
};
📝 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 handleClickOutsideSelection = (event: MouseEvent): void => {
const target = event.target as HTMLElement;
const active = hasActiveSelection();
if (
!articleRef ||
(!target.closest('.menu') && !articleRef.contains(target)) ||
(menuVisible && target.closest('.menu') && articleRef.contains(target))
) {
menuVisible = false;
}
};
const handleClickOutsideSelection = (event: MouseEvent): void => {
const target = event.target as HTMLElement;
const active = hasActiveSelection();
// Hide menu if we click outside the menu and selection is no longer active
if (menuVisible && !target.closest('.menu') && !active) {
menuVisible = false;
}
};

Comment on lines +906 to +916
const highlightText = (): void => {
if (!selectionText) return;

const selectionRange = getSelectionOffsets();

console.log('Highlighting text:', selectionText, selectionRange);
// Implement storing `selectionText`, startOffset, endOffset in DB logic here

menuVisible = false;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Complete the highlight text implementation.

The highlightText function is currently logging but not implementing the actual highlight functionality. The TODO comment indicates that storing in DB logic is missing.

const highlightText = (): void => {
  if (!selectionText) return;

  const selectionRange = getSelectionOffsets();

- console.log('Highlighting text:', selectionText, selectionRange);
- // Implement storing `selectionText`, startOffset, endOffset in DB logic here

+ // Store the highlight in the database
+ if (isLoggedIn && userId && selectionRange) {
+   storeHighlight(selectionText, selectionRange.startOffset, selectionRange.endOffset);
+ } else if (!isLoggedIn) {
+   toast.error('You need to be logged in to highlight text');
+   showAuthBanner = true;
+   bannerText = 'Highlighting articles';
+   bannerSubTitle = joinPhrases(bannerSubtitlePhrases, 'customize');
+ }

  menuVisible = false;
};

+ async function storeHighlight(text: string, startOffset: number, endOffset: number) {
+   try {
+     const { error } = await supabase
+       .from('UserHighlights')
+       .insert({
+         userId,
+         articleId: $slug,
+         text,
+         startOffset,
+         endOffset,
+         createdAt: new Date().toISOString()
+       });
+       
+     if (error) {
+       toast.error('Failed to save highlight. Please try again.');
+       console.error('Failed to save highlight:', error);
+     } else {
+       toast.success('Highlight saved');
+     }
+   } catch (error) {
+     toast.error('An error occurred. Please try again.');
+     console.error('Error saving highlight:', error);
+   }
+ }
📝 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 highlightText = (): void => {
if (!selectionText) return;
const selectionRange = getSelectionOffsets();
console.log('Highlighting text:', selectionText, selectionRange);
// Implement storing `selectionText`, startOffset, endOffset in DB logic here
menuVisible = false;
};
const highlightText = (): void => {
if (!selectionText) return;
const selectionRange = getSelectionOffsets();
// Store the highlight in the database
if (isLoggedIn && userId && selectionRange) {
storeHighlight(selectionText, selectionRange.startOffset, selectionRange.endOffset);
} else if (!isLoggedIn) {
toast.error('You need to be logged in to highlight text');
showAuthBanner = true;
bannerText = 'Highlighting articles';
bannerSubTitle = joinPhrases(bannerSubtitlePhrases, 'customize');
}
menuVisible = false;
};
async function storeHighlight(text: string, startOffset: number, endOffset: number) {
try {
const { error } = await supabase
.from('UserHighlights')
.insert({
userId,
articleId: $slug,
text,
startOffset,
endOffset,
createdAt: new Date().toISOString()
});
if (error) {
toast.error('Failed to save highlight. Please try again.');
console.error('Failed to save highlight:', error);
} else {
toast.success('Highlight saved');
}
} catch (error) {
toast.error('An error occurred. Please try again.');
console.error('Error saving highlight:', error);
}
}

Comment on lines +859 to +889
const handleSelection = (event: MouseEvent): void => {
const selection: Selection | null = window.getSelection();
if (!selection || selection.rangeCount === 0) {
menuVisible = false;
return;
}

const range: Range = selection.getRangeAt(0);

console.log({ range });

// Ensure the selection is inside the article
if (!articleRef || !articleRef.contains(range.commonAncestorContainer)) {
menuVisible = false;
return;
}

selectionText = selection.toString().trim();

if (!selectionText) {
menuVisible = false;
return;
}

// Get bounding box of selection
const rect: DOMRect = range.getBoundingClientRect();
menuX = rect.left + window.scrollX;
menuY = rect.top + window.scrollY - 40; // Position above the selection

menuVisible = true;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remove debug console log in the handleSelection function.

The function logic is good, but there's a debug console.log statement that should be removed.

function handleSelection(event: MouseEvent): void {
  const selection: Selection | null = window.getSelection();
  if (!selection || selection.rangeCount === 0) {
    menuVisible = false;
    return;
  }

  const range: Range = selection.getRangeAt(0);

- console.log({ range });

  // Ensure the selection is inside the article
  if (!articleRef || !articleRef.contains(range.commonAncestorContainer)) {
    menuVisible = false;
    return;
  }

Committable suggestion skipped: line range outside the PR's diff.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant