Skip to content

Bg blur2 and zap threads parent#56

Merged
AustinKelsay merged 22 commits intomainfrom
bgBlur2AndZapThreadsParent
Apr 24, 2025
Merged

Bg blur2 and zap threads parent#56
AustinKelsay merged 22 commits intomainfrom
bgBlur2AndZapThreadsParent

Conversation

@kiwihodl
Copy link
Copy Markdown
Collaborator

@kiwihodl kiwihodl commented Apr 2, 2025

Added ZapThreads to all content types:
Video content
Document content
Combined content
Course pages

Implemented proper access control for comments:
Hidden before payment for paid content
Visible after payment
Visible to subscribers
Visible to content creators
Visible for free content

Added blurBG when modals are open using global css

Summary by CodeRabbit

  • New Features

    • Introduced threaded comments for documents, videos, and course lessons, visible to authorized users based on payment or subscription status.
    • Added access control messaging for comments, indicating restrictions for unauthorized users.
    • Enhanced threaded comment integration with Nostr key encoding for secure user identification.
  • Bug Fixes

    • Improved logic for setting and clearing user key states to ensure correct handling of private and public keys.
  • Style

    • Added new styles for dialog components and backdrops, including dark themes and visual separation for headers, content, and footers.
  • Refactor

    • Refined comment thread logic and authorization checks for improved reliability and maintainability.
    • Reformatted code for readability in several components without changing functionality.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2025

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2025

Walkthrough

This update introduces threaded comment functionality using the ZapThreadsWrapper component across various content types, including courses, videos, documents, and combined content details. The integration is gated by user authorization, which is determined by payment status, subscription, or content ownership. User keys are now encoded in NIP-19 format and passed to the threaded comment component. The ZapThreadsWrapper itself is enhanced with stricter prop handling, improved script and DOM management, error handling, and authorization gating. Additionally, dialog styles are added to the global CSS, and several code formatting improvements are made for clarity and consistency.

Changes

File(s) Change Summary
src/components/ZapThreadsWrapper.js Enhanced with isAuthorized prop to gate rendering and script loading; improved relay attribute sanitization; added error event listeners; stricter DOM cleanup removing all children; always sets disable attribute; returns null if unauthorized; updated effect dependencies to include isAuthorized.
src/components/content/carousels/templates/CourseTemplate.js Reformatted useZapsSubscription call and JSX className attributes for readability; no functional changes.
src/components/content/combined/CombinedDetails.js
src/components/content/documents/DocumentDetails.js
src/components/content/videos/VideoDetails.js
Imported ZapThreadsWrapper, appConfig, and nip19; added state variables for NIP-19 encoded user private (nsec) and public (npub) keys; added effect hooks to encode keys on session changes; conditional rendering of ZapThreadsWrapper or access restriction message based on user authorization and content status; minor formatting improvements in hooks and JSX.
src/pages/course/[slug]/index.js Integrated ZapThreadsWrapper for threaded comments on lessons; added state for lesson Nostr addresses and user keys; effect hooks generate Nostr addresses per lesson and encode user keys; conditional rendering of ZapThreadsWrapper or access restriction message per lesson; minor formatting adjustments to dynamic imports and filters; no changes to core course data or payment logic.
src/components/feeds/messages/CommunityMessage.js
src/pages/details/[slug]/index.js
Revised logic for setting nsec and npub states to ensure mutual exclusivity and clear both if no keys present; introduced isAuthorized boolean in details page; updated ZapThreadsWrapper usage to include isAuthorized prop and conditional rendering based on authorization; removed previous fallback rendering.
src/styles/globals.css Added CSS classes .p-dialog-mask, .p-dialog, .p-dialog-header, .p-dialog-content, and .p-dialog-footer defining dialog background, border, shadows, text colors, and backdrop blur effect.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ContentPage
    participant ZapThreadsWrapper
    participant NostrScript

    User ->> ContentPage: Loads content (course, video, doc, etc.)
    ContentPage ->> ContentPage: Check session, payment, subscription
    ContentPage ->> ContentPage: Encode user keys (nsec/npub)
    ContentPage ->> ZapThreadsWrapper: Render if authorized (pass anchor, user, relays, disable, isAuthorized)
    ZapThreadsWrapper ->> ZapThreadsWrapper: If not authorized, render nothing
    ZapThreadsWrapper ->> NostrScript: Inject script and zap-threads element
    NostrScript -->> ZapThreadsWrapper: On load or error events
    ZapThreadsWrapper ->> ZapThreadsWrapper: Cleanup DOM and event listeners on unmount
Loading

Poem

In a warren of code, new threads now appear,
With keys encoded, comments draw near.
Dialogs shimmer with a shadowy glow,
Only the worthy may join in the flow.
If you’re a subscriber, creator, or paid,
The rabbit’s new comments are perfectly laid!
🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed7dda and 833ebf3.

📒 Files selected for processing (3)
  • src/components/content/documents/DocumentDetails.js (4 hunks)
  • src/components/content/videos/VideoDetails.js (4 hunks)
  • src/pages/details/[slug]/index.js (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/content/documents/DocumentDetails.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/details/[slug]/index.js
  • src/components/content/videos/VideoDetails.js
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

Copy link
Copy Markdown
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: 4

🧹 Nitpick comments (13)
src/components/content/combined/CombinedDetails.js (1)

127-127: Consider using optional chaining for better readability

The code accesses nested properties that might be undefined. Using optional chaining would make the code more concise and less error-prone.

-          if (res.data && res.data.lessons[0]?.courseId) {
+          if (res.data?.lessons[0]?.courseId) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 127-127: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseLesson.js (1)

189-190: Consider using optional chaining for brevity

Instead of checking each property with lesson && lesson.topics && lesson.topics.length, simplify it with optional chaining:

- {lesson && lesson.topics && lesson.topics.length > 0 && ...
+ {lesson?.topics?.length > 0 && ...
🧰 Tools
🪛 Biome (1.9.4)

[error] 189-190: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CourseTemplate.js (1)

134-147: Consider using optional chaining to simplify checks

Replace course && course.topics && course.topics.map(...) with course?.topics?.map(...) for cleaner, safer code.

- {course && course.topics && course.topics.map(...)}
+ {course?.topics?.map(...)}
🧰 Tools
🪛 Biome (1.9.4)

[error] 134-147: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/documents/DocumentDetails.js (4)

67-69: Consider using optional chaining for error handling

Multiple checks can be streamlined. For example, error.response?.data?.error is cleaner than using multiple && checks.

🧰 Tools
🪛 Biome (1.9.4)

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


77-79: Consider using optional chaining for error handling

Again, you can unify nested checks by using error.response?.data?.error.

🧰 Tools
🪛 Biome (1.9.4)

[error] 77-79: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


142-142: Consider using optional chaining

You can simplify this condition by leveraging res.data?.lessons?.[0]?.courseId directly.

- if (res.data && res.data.lessons[0]?.courseId) {
+ if (res.data?.lessons?.[0]?.courseId) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 142-142: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


162-162: Use optional chaining for user role

Simplify to session?.user?.role?.subscribed rather than checking each level separately.

- if (session?.user && session.user?.role?.subscribed && decryptedContent) {
+ if (session?.user?.role?.subscribed && decryptedContent) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 162-162: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/pages/course/[slug]/index.js (4)

25-92: Consider consolidating repeated connection calls
Inside the useCourseData hook, ndk.connect() is invoked each time the effect runs. If the effect re-executes frequently, repeatedly calling ndk.connect() could become inefficient. Consider caching the connection or verifying that you only reconnect when needed to reduce overhead.


94-139: Use a parallel fetching approach for multiple lessons
In the useLessons hook, you call fetchLesson() within a forEach loop. For better performance and streamlined error handling, consider using Promise.all to fetch all lessons concurrently:

- lessonIds.forEach((lessonId) => fetchLesson(lessonId));
+ await Promise.all(lessonIds.map((lessonId) => fetchLesson(lessonId)));

141-179: Guard against large content decryption edge cases
Decryption is performed for all lessons in a single pass. If lessons contain large encrypted payloads, you could face performance or memory issues. Consider chunking decryption or caching already-decrypted content to prevent repeated decryption calls.


181-427: Consider optional chaining for safe property access
When checking response.preimage in handlePaymentSuccess (line 278), using response?.preimage can guard against potential null or undefined references.

🧰 Tools
🪛 Biome (1.9.4)

[error] 278-278: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseDetails.js (1)

137-212: Watch for potential performance overhead with high zap volumes
If a course becomes very popular and accumulates many zaps, rendering them all at once might cause slowdowns. Consider batching or paginating the display if you anticipate a large volume.

🧰 Tools
🪛 Biome (1.9.4)

[error] 139-140: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/videos/VideoDetails.js (1)

161-243: Optional chaining could improve resilience
At line 162, consider using session?.user?.role?.subscribed to guard against cases where session.user might be undefined.

🧰 Tools
🪛 Biome (1.9.4)

[error] 162-162: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f887848 and d9dad92.

📒 Files selected for processing (10)
  • src/components/ZapThreadsWrapper.js (1 hunks)
  • src/components/content/carousels/templates/CourseTemplate.js (5 hunks)
  • src/components/content/combined/CombinedDetails.js (1 hunks)
  • src/components/content/courses/CourseDetails.js (1 hunks)
  • src/components/content/courses/CourseLesson.js (1 hunks)
  • src/components/content/documents/DocumentDetails.js (1 hunks)
  • src/components/content/videos/VideoDetails.js (1 hunks)
  • src/config/appConfig.js (1 hunks)
  • src/pages/course/[slug]/index.js (1 hunks)
  • src/styles/globals.css (1 hunks)
🧰 Additional context used
🧬 Code Definitions (7)
src/components/content/combined/CombinedDetails.js (4)
src/components/content/documents/DocumentDetails.js (6)
  • course (43-43)
  • session (49-49)
  • useToast (50-50)
  • isMobileView (52-52)
  • nsec (55-55)
  • npub (56-56)
src/components/content/courses/CourseDetails.js (6)
  • session (44-44)
  • useToast (45-45)
  • isMobileView (47-47)
  • nsec (37-37)
  • npub (38-38)
  • nAddress (36-36)
src/components/ui/MoreOptionsMenu.js (1)
  • MoreOptionsMenu (14-67)
src/config/appConfig.js (1)
  • appConfig (1-57)
src/components/content/documents/DocumentDetails.js (2)
src/hooks/nostrQueries/zaps/useZapsSubscription.js (3)
  • useZapsSubscription (5-82)
  • zaps (6-6)
  • zapsLoading (7-7)
src/config/appConfig.js (1)
  • appConfig (1-57)
src/components/content/courses/CourseLesson.js (2)
src/hooks/tracking/useTrackDocumentLesson.js (5)
  • session (11-11)
  • useTrackDocumentLesson (5-109)
  • markLessonAsCompleted (54-75)
  • isCompleted (6-6)
  • isTracking (8-8)
src/components/ZapThreadsWrapper.js (1)
  • ZapThreadsWrapper (3-98)
src/pages/course/[slug]/index.js (5)
src/components/content/courses/CombinedLesson.js (1)
  • CombinedLesson (26-301)
src/components/content/courses/VideoLesson.js (1)
  • VideoLesson (25-267)
src/components/content/courses/DocumentLesson.js (1)
  • DocumentLesson (25-214)
src/components/content/courses/CourseDetails.js (1)
  • CourseDetails (26-332)
src/hooks/encryption/useDecryptContent.js (4)
  • error (6-6)
  • useDecryptContent (4-30)
  • useDecryptContent (4-30)
  • decryptContent (8-27)
src/components/content/carousels/templates/CourseTemplate.js (3)
src/components/zaps/ZapForm.js (1)
  • nAddress (6-11)
src/config/appConfig.js (1)
  • appConfig (1-57)
src/components/ZapThreadsWrapper.js (1)
  • ZapThreadsWrapper (3-98)
src/components/content/videos/VideoDetails.js (3)
src/hooks/nostrQueries/zaps/useZapsSubscription.js (3)
  • useZapsSubscription (5-82)
  • zaps (6-6)
  • zapsLoading (7-7)
src/components/ui/MoreOptionsMenu.js (2)
  • menuRef (20-20)
  • MoreOptionsMenu (14-67)
src/config/appConfig.js (1)
  • appConfig (1-57)
src/components/content/courses/CourseDetails.js (2)
src/components/bitcoinConnect/CoursePaymentButton.js (6)
  • router (29-29)
  • session (27-27)
  • useToast (26-26)
  • windowWidth (30-30)
  • CoursePaymentButton (23-236)
  • handlePaymentSuccess (95-117)
src/utils/nostr.js (5)
  • pubkey (31-31)
  • fields (4-4)
  • findKind0Fields (3-50)
  • findKind0Fields (3-50)
  • i (223-223)
🪛 Biome (1.9.4)
src/components/content/combined/CombinedDetails.js

[error] 127-127: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/documents/DocumentDetails.js

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 77-79: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 142-142: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 162-162: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseLesson.js

[error] 71-71: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 189-190: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/pages/course/[slug]/index.js

[error] 278-278: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CourseTemplate.js

[error] 134-147: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/videos/VideoDetails.js

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 77-79: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 162-162: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseDetails.js

[error] 139-140: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 260-263: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (23)
src/config/appConfig.js (1)

12-12: Added new public key to author list

A new public key has been added to the authorPubkeys array, which is likely needed for the ZapThreads functionality mentioned in the PR objectives. This allows another author to be recognized by the system.

src/styles/globals.css (1)

153-179: Enhanced modal experience with backdrop blur

The addition of dialog backdrop styles improves the UI by creating a visual hierarchy that helps users focus on modal content. The blur effect and semi-transparent background match the PR objectives of adding background blur when modals are open.

These styles provide a consistent dark theme for dialogs with appropriate contrast and visual separation from the underlying content.

src/components/ZapThreadsWrapper.js (7)

3-10: Added conditional rendering based on authorization

The component signature has been updated to include the isAuthorized prop, which will control whether the ZapThreads functionality is loaded. This aligns with the PR objective of implementing refined access control.


14-18: Early exit prevents loading for unauthorized users

Good addition of an early return in the useEffect when the user isn't authorized. This prevents unnecessary script loading and improves performance.


30-48: Improved relay URL handling and attribute setting

The code now properly cleans up relay URLs by trimming whitespace and filtering empty entries, making the component more robust against malformed input. The attribute handling has also been improved with consistent setting of the disable attribute.


49-52: Added error handling for ZapThreads element

Adding error handling for the ZapThreads element is a good practice that will help with debugging issues in production.


69-71: Added script loading error handling

Good addition of error handling for script loading failures, which provides better visibility into potential issues.


78-81: Simplified cleanup to remove child elements

The cleanup function has been simplified to remove all child elements from zapRef without checking for the existence of a first child, making the code more maintainable.


88-92: Added conditional rendering based on authorization status

The component now conditionally returns null when the user isn't authorized, preventing the rendering of the div element. This supports the PR objective of improving access control for comments.

src/components/content/combined/CombinedDetails.js (5)

18-20: Added imports for ZapThreads functionality

These imports are necessary for implementing the ZapThreads functionality mentioned in the PR objectives. The nip19 from the nostr-tools library is used for encoding keys.


22-24: Simplified MDDisplay dynamic import

The MDDisplay component import has been refactored to use a more concise syntax while maintaining the same functionality with server-side rendering disabled.


53-54: Added state variables for nostr keys

Added state variables nsec and npub to store encoded values derived from the user's session data, necessary for the ZapThreads implementation.


144-151: Added key encoding for nostr integration

This effect properly encodes the user's private or public key for use with nostr protocols, which is essential for the ZapThreads functionality.


341-361: Implemented conditional ZapThreads rendering based on access control

This implementation aligns perfectly with the PR objectives. It shows ZapThreads for:

  1. Free content
  2. Paid content that has been purchased
  3. Content viewed by subscribers
  4. Content viewed by the creator

For users who haven't purchased the content, a helpful message is displayed instead of the comments.

src/components/content/courses/CourseLesson.js (1)

60-88: Mark-as-completed logic looks great!

This approach effectively allows users to track lesson completion with proper error handling and success messages.

🧰 Tools
🪛 Biome (1.9.4)

[error] 71-71: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CourseTemplate.js (1)

69-77: Potential security concern: storing private key in component state

Holding private keys in memory can be risky if accidentally logged or exposed. Ensure it is absolutely required and confirm your security requirements.

src/components/content/documents/DocumentDetails.js (1)

356-363: Properly passing isAuthorized

Great job including isAuthorized={true}. This ensures the ZapThreads script will load and execute as intended.

src/pages/course/[slug]/index.js (1)

9-23: All newly added imports look good
No issues found with these additions.

src/components/content/courses/CourseDetails.js (2)

1-24: Imports and initial declarations appear clean
The new references, including ZapThreadsWrapper and config imports, are correct and consistent.


222-333: Content gating logic for ZapThreads is well-organized
The checks for course owners, purchasers, and subscribers effectively secure paid comments. Good job keeping the logic consistent with the rest of the code.

🧰 Tools
🪛 Biome (1.9.4)

[error] 260-263: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/videos/VideoDetails.js (3)

18-20: Newly introduced imports appear correct
No issues found with the addition of ZapThreadsWrapper, appConfig, or nip19.


22-24: Dynamic import for MDDisplay is well-implemented
Using ssr: false for rendering Markdown can help optimize performance for large content.


295-389: UI content gating logic is straightforward
Displaying the ZapThreadsWrapper for authorized users only is consistent and aligns with the paid vs. free content model.

Copy link
Copy Markdown
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: 13

♻️ Duplicate comments (6)
src/components/content/carousels/templates/CourseTemplate.js (2)

204-211: Add isAuthorized prop to ZapThreadsWrapper

The ZapThreadsWrapper component won't initialize its script if the isAuthorized prop is falsy. Since this component is already conditionally rendered based on payment/subscription status, you should explicitly pass isAuthorized={true}.

 <ZapThreadsWrapper
   anchor={nAddress}
   user={nsec || npub || null}
   relays={appConfig.defaultRelayUrls.join(',')}
   disable="zaps"
+  isAuthorized={true}
 />

212-218: Add isAuthorized prop to ZapThreadsWrapper

The second instance of ZapThreadsWrapper also needs the isAuthorized prop to ensure proper initialization.

 <ZapThreadsWrapper
   anchor={nAddress}
   user={npub}
   relays={appConfig.defaultRelayUrls.join(',')}
   disable="zaps"
+  isAuthorized={true}
 />
src/components/content/courses/CourseLesson.js (1)

234-251: ⚠️ Potential issue

isAuthorized prop is still missing – ZapThreads will never render
We previously flagged this but it remains unfixed. ZapThreadsWrapper injects the script only when isAuthorized is truthy. Right now the component is always rendered without that prop, so comments silently fail to load.

-<ZapThreadsWrapper
-  anchor={nAddress}
-  user={nsec || npub || null}
-  relays={appConfig.defaultRelayUrls.join(',')}
-  disable="zaps"
-/>
+<ZapThreadsWrapper
+  anchor={nAddress}
+  user={nsec || npub || null}
+  relays={appConfig.defaultRelayUrls.join(',')}
+  disable="zaps"
+  isAuthorized={true}
+/>
src/components/content/videos/VideoDetails.js (3)

58-82: Centralise deletion logic

handleDelete duplicates logic found in several components. Extract to a shared hook or helper to simplify maintenance.

🧰 Tools
🪛 Biome (1.9.4)

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 76-76: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


145-151: ⚠️ Potential issue

Same Buffer issue as in DocumentDetails

For consistency and reliability across builds, add

+import { Buffer } from 'buffer';

or drop Buffer entirely and pass the hex string directly to nip19.nsecEncode, as described in the previous file.


96-101: 🛠️ Refactor suggestion

Add "noopener" to window.open calls

Replicate the same hardening as suggested for DocumentDetails.js.

-window.open(`https://habla.news/a/${nAddress}`, '_blank');
+window.open(`https://habla.news/a/${nAddress}`, '_blank', 'noopener');

And likewise for the course opener.

[security]

Also applies to: 114-120

🧹 Nitpick comments (43)
src/components/content/carousels/templates/CombinedTemplate.js (5)

33-42: Simplify resource null check using optional chaining.
The condition if (resource && resource?.d) can be reduced to if (resource?.d), since optional chaining already handles null/undefined for resource.

Consider applying:

-useEffect(() => {
-  if (resource && resource?.d) {
+useEffect(() => {
+  if (resource?.d) {
     const nAddress = nip19.naddrEncode({
       pubkey: resource.pubkey,
       kind: resource.kind,
       identifier: resource.d,
     });
     setNAddress(nAddress);
   }
 }, [resource]);
🧰 Tools
🪛 Biome (1.9.4)

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


51-56: Consider memoizing shouldShowMetaTags or extracting the static list.
Currently shouldShowMetaTags is redefined on every render. Since it relies on a constant array (['lesson', 'document', 'video', 'course']), you could extract this list outside the component or wrap the function in useCallback to avoid unnecessary re-creations when passed down.


67-74: Enhance the alt attribute for accessibility.
The alt text is hardcoded as "resource thumbnail". Using the resource title will provide more meaningful context for screen readers:

- alt="resource thumbnail"
+ alt={`${resource.title} thumbnail`}

97-104: Use a stable key instead of array index in list rendering.
Using the array index as key can cause rendering glitches when the topics array changes order. If each topic is unique, prefer it as the key:

- {resource?.topics?.map((topic, index) =>
-   shouldShowMetaTags(topic) && (
-     <Tag size="small" key={index} className="px-2 py-1 text-sm text-[#f8f8ff]">
-       {topic}
-     </Tag>
-   )
- )}
+ {resource?.topics?.map(topic =>
+   shouldShowMetaTags(topic) && (
+     <Tag size="small" key={topic} className="px-2 py-1 text-sm text-[#f8f8ff]" value={topic} />
+   )
+ )}

125-140: Move inline styles into CSS for maintainability.
The CardDescription uses several inline styles (e.g., overflow, display, WebkitLineClamp). Extract these into a CSS class or styled component to keep JSX cleaner and improve reusability.

src/components/forms/course/PublishedCourseForm.js (1)

23-25: Consider simplifying the boolean state initialization.

The ternary expression to convert a value to boolean is unnecessary.

-const [isPaidCourse, setIsPaidCourse] = useState(
-  course?.price && course?.price > 0 ? true : false
-);
+const [isPaidCourse, setIsPaidCourse] = useState(
+  !!(course?.price && course?.price > 0)
+);
🧰 Tools
🪛 Biome (1.9.4)

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/video/EditDraftVideoForm.js (1)

20-20: Consider simplifying the boolean state initialization.

The ternary expression is unnecessary when converting a value to boolean.

-const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false);
+const [isPaidResource, setIsPaidResource] = useState(!!draft?.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 20-20: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/content/carousels/templates/DocumentTemplate.js (1)

34-34: Consider using optional chaining for cleaner code.

The current condition check can be simplified using optional chaining.

-    if (document && document?.d) {
+    if (document?.d) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/DraftCourseLesson.js (1)

35-40: Simplify conditional rendering with optional chaining.

The nested conditional checks can be simplified using optional chaining for better readability.

-              {lesson &&
-                lesson.topics &&
-                lesson.topics.length > 0 &&
-                lesson.topics.map((topic, index) => (
-                  <Tag className="mr-2 text-white" key={index} value={topic}></Tag>
-                ))}
+              {lesson?.topics?.length > 0 &&
+                lesson.topics.map((topic, index) => (
+                  <Tag className="mr-2 text-white" key={index} value={topic}></Tag>
+                ))}
🧰 Tools
🪛 Biome (1.9.4)

[error] 35-36: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/banner/HeroBanner.js (1)

103-105: Improve error handling for user feedback.

The current implementation only logs errors to console when session data is incomplete. Consider adding user-facing feedback for a better user experience.

        } else {
          console.error('Session data incomplete:', session);
+          // Show user-facing error notification
+          toast.error('Unable to complete sign in. Please try again.');
        }
src/components/content/carousels/templates/VideoTemplate.js (1)

33-42: Use optional chaining to simplify null checks.
The condition if (video && video?.d) can be reduced to if (video?.d), removing the redundant existence check and leveraging optional chaining.

🧰 Tools
🪛 Biome (1.9.4)

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/course/embedded/EmbeddedDocumentForm.js (1)

24-24: Simplify boolean assignment.
In setIsPaidResource(draft.price ? true : false), the ternary is unnecessary. Use setIsPaidResource(!!draft.price) for clarity.

Also applies to: 50-50

src/components/feeds/MessageInput.js (1)

44-50: Simplify nested checks with optional chaining.
Replace the redundant if (session && session?.user && session.user?.privkey) with if (session?.user?.privkey) to make the code more concise.

🧰 Tools
🪛 Biome (1.9.4)

[error] 46-46: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/course/embedded/EmbeddedVideoForm.js (2)

28-30: Remove debug logging.
The console.log('session', session.user) appears to be a leftover debug statement. Consider removing it or using a proper logging utility.


38-38: Simplify boolean assignment.
Use setIsPaidResource(!!draft.price) instead of the verbose draft.price ? true : false.

🧰 Tools
🪛 Biome (1.9.4)

[error] 38-38: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/document/EditPublishedDocumentForm.js (1)

30-30: Simplify boolean initialization.
Change useState(event.price ? true : false) to useState(!!event.price) to eliminate the unnecessary ternary.

🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/course/CourseForm.js (2)

21-21: Simplify the boolean ternary expression.

The boolean ternary expression can be simplified for better readability.

- const [isPaidCourse, setIsPaidCourse] = useState(draft?.price ? true : false);
+ const [isPaidCourse, setIsPaidCourse] = useState(!!draft?.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 21-21: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


92-92: Simplify the boolean ternary expression.

The boolean ternary expression can be simplified for better readability.

- const isResource = lessonDraft?.kind ? true : false;
+ const isResource = !!lessonDraft?.kind;
🧰 Tools
🪛 Biome (1.9.4)

[error] 92-92: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/content/carousels/InteractivePromotionalCarousel.js (1)

71-85: Improve WebLN handling with optional chaining.

The WebLN checks could be improved using optional chaining to make the code more concise and readable.

- if (window && window?.webln && window?.webln?.lnurl) {
+ if (window?.webln?.lnurl) {
  await window.webln.enable();
-  const result = await window.webln.lnurl('austin@bitcoinpleb.dev');
-  if (result && result?.preimage) {
+  const result = await window.webln.lnurl('austin@bitcoinpleb.dev');
+  if (result?.preimage) {
    showToast('success', 'Payment Sent', 'Thank you for your donation!');
  }
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 75-75: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 78-78: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/DocumentLesson.js (1)

56-56: Apply optional chaining for safer function calls.

Consider using optional chaining when calling functions that might be undefined.

- setCompleted && setCompleted(lesson.id);
+ setCompleted?.(lesson.id);
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/document/EditDraftDocumentForm.js (1)

19-19: Simplify the boolean ternary expression.

The boolean ternary expression can be simplified for better readability.

- const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false);
+ const [isPaidResource, setIsPaidResource] = useState(!!draft?.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 19-19: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/combined/EditDraftCombinedResourceForm.js (2)

24-24: Boolean conditional expression could be simplified.

The ternary operation with boolean literals is unnecessary and can be simplified.

-  const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false);
+  const [isPaidResource, setIsPaidResource] = useState(!!draft?.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


48-50: Consider using optional chaining for nested conditionals.

The nested conditional checks could be simplified using optional chaining.

-  if (match && match[1]) {
-    extractedUrl = `https://www.youtube.com/watch?v=${match[1]}`;
-  }
+  if (match?.[1]) {
+    extractedUrl = `https://www.youtube.com/watch?v=${match[1]}`;
+  }
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/combined/EditPublishedCombinedResourceForm.js (3)

31-31: Boolean conditional expression could be simplified.

Similar to the previous file, this ternary operation with boolean literals is unnecessary.

-  const [isPaidResource, setIsPaidResource] = useState(event.price ? true : false);
+  const [isPaidResource, setIsPaidResource] = useState(!!event.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 31-31: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


143-147: Consider using optional chaining for nested conditionals.

The nested conditional check could be simplified using optional chaining.

-  customTopics.forEach(topic => {
-    if (topic && topic.trim()) {
-      ndkEvent.tags.push(['t', topic.trim().toLowerCase()]);
-    }
-  });
+  customTopics.forEach(topic => {
+    if (topic?.trim()) {
+      ndkEvent.tags.push(['t', topic.trim().toLowerCase()]);
+    }
+  });
🧰 Tools
🪛 Biome (1.9.4)

[error] 144-144: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


150-154: Similar optional chaining opportunity for link validation.

The link validation logic could also benefit from optional chaining.

-  additionalLinks.forEach(link => {
-    if (link && link.trim()) {
-      ndkEvent.tags.push(['r', link.trim()]);
-    }
-  });
+  additionalLinks.forEach(link => {
+    if (link?.trim()) {
+      ndkEvent.tags.push(['r', link.trim()]);
+    }
+  });
🧰 Tools
🪛 Biome (1.9.4)

[error] 151-151: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/course/LessonSelector.js (1)

40-43: Optimize optional chaining for better readability

Consider using optional chaining for the nested property access to simplify the code and improve readability.

 const contentPrice =
   content?.price ||
-  (content?.tags && content?.tags.find(tag => tag[0] === 'price')?.[1]) ||
+  content?.tags?.find(tag => tag[0] === 'price')?.[1] ||
   0;
🧰 Tools
🪛 Biome (1.9.4)

[error] 42-42: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/video/EditPublishedVideoForm.js (1)

31-31: Simplify boolean conversion

The ternary operator here is unnecessarily verbose since it's just converting a value to a boolean.

- const [isPaidResource, setIsPaidResource] = useState(event.price ? true : false);
+ const [isPaidResource, setIsPaidResource] = useState(!!event.price);
🧰 Tools
🪛 Biome (1.9.4)

[error] 31-31: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/content/courses/DraftCourseDetails.js (2)

241-241: Check for null topics array before mapping

When mapping over the topics array in the tags, ensure that the topics array exists to avoid potential errors.

- ...processedEvent?.topics?.map(topic => ['t', topic]),
+ ...(processedEvent?.topics ? processedEvent.topics.map(topic => ['t', topic]) : []),
🧰 Tools
🪛 Biome (1.9.4)

[error] 241-241: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)


393-399: Optimize conditional rendering with optional chaining

Use optional chaining to simplify the conditional logic for rendering topic tags.

- {processedEvent &&
-   processedEvent.topics &&
-   processedEvent.topics.length > 0 &&
-   processedEvent.topics.map((topic, index) => (
+ {processedEvent?.topics?.length > 0 &&
+   processedEvent.topics.map((topic, index) => (
      <Tag className="mr-2 text-white" key={index} value={topic}></Tag>
    ))}
🧰 Tools
🪛 Biome (1.9.4)

[error] 393-394: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CourseTemplate.js (1)

132-142: Optimize conditional rendering with optional chaining

Use optional chaining to simplify the conditional logic for rendering topic tags.

- {course &&
-   course.topics &&
-   course.topics.map(
+ {course?.topics?.map(
      (topic, index) =>
        shouldShowMetaTags(topic) && (
          <Tag size="small" key={index} className="px-2 py-1 text-sm text-[#f8f8ff]">
            {topic}
          </Tag>
        )
    )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 132-141: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseDetails.js (1)

64-83: Avoid rendering menu items with null labels
MoreOptionsMenu receives all items, even those where visible is false. Because the first two items still carry label: null, downstream UI libraries may warn or display empty menu rows. Either filter by visible before passing, or omit label/icon entirely when hidden.

-const menuItems = [
-  {
-    label: processedEvent?.pubkey === session?.user?.pubkey ? 'Edit' : null,
+const menuItems = [
+  processedEvent?.pubkey === session?.user?.pubkey && {
+    label: 'Edit',
     icon: 'pi pi-pencil',
...
 ].filter(Boolean);
src/components/content/documents/DocumentDetails.js (2)

53-54: Dead code – menuRef is never used

const menuRef = useRef(null); is declared but never referenced afterwards.
Please remove it to reduce noise and avoid misleading future readers.


58-82: Duplicate deletion logic across components

handleDelete is identical (or almost identical) in multiple content components (VideoDetails, CourseDetails, etc.). Extract this into a shared hook or utility (e.g., useResourceDeletion) to honour DRY and make future fixes centralised.

🧰 Tools
🪛 Biome (1.9.4)

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 76-76: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/videos/VideoDetails.js (2)

53-54: Unused variable – menuRef

menuRef is declared but never used.
Delete it to keep the component lean.


137-143: Missed dependency & zero‑zap state

setZapAmount is invoked only when zaps.length > 0. If a resource loses its only zap (edge‑case but possible after re‑org / filter), zapAmount would retain a stale value.
Consider computing on every change:

- if (zaps.length > 0) {
-   const total = getTotalFromZaps(zaps, processedEvent);
-   setZapAmount(total);
- }
+const total = getTotalFromZaps(zaps, processedEvent);
+setZapAmount(total);
src/components/bitcoinConnect/SubscriptionPaymentButton.js (7)

96-159: Consolidate and refactor recurring-subscription flow
This handler is quite large and shadows the imported nwc with a local variable of the same name, which can be confusing and error-prone. Also, many configuration values (maxAmount, budgetRenewal, etc.) are hard‐coded and duplicated elsewhere. Consider:

  • Renaming the imported nwc or the local const nwc = to avoid shadowing.
  • Extracting NWC initialization and subscription‐API logic into smaller helper functions.
  • Centralizing constants (amount, renewal interval) at module level or in a config file.

Example diff snippet for the naming fix:

-import { webln, nwc } from '@getalby/sdk';
+import { webln } from '@getalby/sdk';
...
- const newNwc = nwc.NWCClient.withNewSecret();
+ const newNwc = webln.NWCClient.withNewSecret();
...
- const nwc = new webln.NostrWebLNProvider({ ... });
+ const nostrProvider = new webln.NostrWebLNProvider({ ... });

This reduces confusion and aligns with DRY principles.


161-215: DRY up manual NWC subscription logic
handleManualNwcSubmit largely duplicates parts of handleRecurringSubscription (invoice fetch, payment, subscription API). Extract common steps into shared utilities to reduce maintenance overhead.


219-235: Improve unauthenticated UX instead of console.log
Current code logs 'unauthenticated' before redirecting. For a better developer experience, you could show a toast or disable the button when status === 'unauthenticated':

- onClick={async () => {
-   if (status === 'unauthenticated') {
-     console.log('unauthenticated');
-     router.push('/auth/signin');
-   } else { ... }
- }}
+ onClick={async () => {
+   if (status !== 'authenticated') {
+     showToast('info', 'Sign In Required', 'Please sign in to continue');
+     router.push('/auth/signin');
+     return;
+   }
+   ...
+ }}

This change offers clearer feedback.


240-263: Consistent authentication gating for recurring setup
Similarly, replace console.log('unauthenticated') with a user-facing notification or disable the option when unauthenticated.


268-287: Validate NWC URL format
The manual input field accepts any string. You might enforce a URL pattern or render validation feedback before attempting to enable() it:

<input
  type="url"
  pattern="^nostr-wc:[A-Za-z0-9_-]+$"
  ...
/>

This ensures early client-side validation.


291-299: Simplify the conditional rendering using optional chaining
You can use optional chaining to reduce verbosity:

- {invoice && invoice.paymentRequest && (
+ {invoice?.paymentRequest && (

And inside:

- invoice={invoice?.paymentRequest}
+ invoice={invoice.paymentRequest}

This keeps the JSX concise and clear.

🧰 Tools
🪛 Biome (1.9.4)

[error] 291-291: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


306-306: Align file name with component name
The file is named SubscriptionPaymentButton.js but exports SubscriptionPaymentButtons. Consider renaming the file to SubscriptionPaymentButtons.js for consistency.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d9dad92 and 23c9d1f.

📒 Files selected for processing (67)
  • .prettierrc (1 hunks)
  • README.md (6 hunks)
  • components.json (1 hunks)
  • jsconfig.json (1 hunks)
  • next.config.js (2 hunks)
  • package.json (1 hunks)
  • src/components/BottomBar.js (1 hunks)
  • src/components/Layout.js (1 hunks)
  • src/components/MoreInfo.js (1 hunks)
  • src/components/ZapThreadsWrapper.js (2 hunks)
  • src/components/banner/HeroBanner.js (1 hunks)
  • src/components/bitcoinConnect/BitcoinConnect.js (3 hunks)
  • src/components/bitcoinConnect/CoursePaymentButton.js (1 hunks)
  • src/components/bitcoinConnect/ResourcePaymentButton.js (7 hunks)
  • src/components/bitcoinConnect/SubscriptionPaymentButton.js (1 hunks)
  • src/components/buttons/AlbyButton.js (1 hunks)
  • src/components/buttons/GenericButton.js (1 hunks)
  • src/components/charts/ActivityContributionChart.js (1 hunks)
  • src/components/charts/CombinedContributionChart.js (1 hunks)
  • src/components/content/SelectedContentItem.js (1 hunks)
  • src/components/content/carousels/CoursesCarousel.js (1 hunks)
  • src/components/content/carousels/DocumentsCarousel.js (1 hunks)
  • src/components/content/carousels/GenericCarousel.js (1 hunks)
  • src/components/content/carousels/InteractivePromotionalCarousel.js (6 hunks)
  • src/components/content/carousels/MessagesCarousel.js (1 hunks)
  • src/components/content/carousels/VideosCarousel.js (1 hunks)
  • src/components/content/carousels/skeletons/TemplateSkeleton.js (1 hunks)
  • src/components/content/carousels/templates/CombinedTemplate.js (1 hunks)
  • src/components/content/carousels/templates/CourseTemplate.js (4 hunks)
  • src/components/content/carousels/templates/DocumentTemplate.js (1 hunks)
  • src/components/content/carousels/templates/VideoTemplate.js (1 hunks)
  • src/components/content/combined/CombinedDetails.js (1 hunks)
  • src/components/content/courses/CombinedLesson.js (1 hunks)
  • src/components/content/courses/CourseDetails.js (1 hunks)
  • src/components/content/courses/CourseLesson.js (1 hunks)
  • src/components/content/courses/DocumentLesson.js (1 hunks)
  • src/components/content/courses/DraftCourseDetails.js (1 hunks)
  • src/components/content/courses/DraftCourseLesson.js (1 hunks)
  • src/components/content/courses/VideoLesson.js (1 hunks)
  • src/components/content/documents/DocumentDetails.js (1 hunks)
  • src/components/content/dropdowns/ContentDropdownItem.js (1 hunks)
  • src/components/content/dropdowns/MessageDropdownItem.js (1 hunks)
  • src/components/content/lists/ContentList.js (1 hunks)
  • src/components/content/lists/ContentListItem.js (1 hunks)
  • src/components/content/lists/ProgressListItem.js (1 hunks)
  • src/components/content/lists/PurchasedListItem.js (1 hunks)
  • src/components/content/videos/VideoDetails.js (1 hunks)
  • src/components/feeds/DiscordFeed.js (1 hunks)
  • src/components/feeds/GlobalFeed.js (1 hunks)
  • src/components/feeds/MessageInput.js (1 hunks)
  • src/components/feeds/NostrFeed.js (1 hunks)
  • src/components/feeds/StackerNewsFeed.js (1 hunks)
  • src/components/feeds/messages/CommunityMessage.js (1 hunks)
  • src/components/forms/combined/CombinedResourceForm.js (1 hunks)
  • src/components/forms/combined/EditDraftCombinedResourceForm.js (1 hunks)
  • src/components/forms/combined/EditPublishedCombinedResourceForm.js (1 hunks)
  • src/components/forms/course/CourseForm.js (1 hunks)
  • src/components/forms/course/LessonSelector.js (1 hunks)
  • src/components/forms/course/PublishedCourseForm.js (1 hunks)
  • src/components/forms/course/embedded/EmbeddedDocumentForm.js (1 hunks)
  • src/components/forms/course/embedded/EmbeddedVideoForm.js (1 hunks)
  • src/components/forms/document/DocumentForm.js (1 hunks)
  • src/components/forms/document/EditDraftDocumentForm.js (1 hunks)
  • src/components/forms/document/EditPublishedDocumentForm.js (1 hunks)
  • src/components/forms/video/EditDraftVideoForm.js (1 hunks)
  • src/components/forms/video/EditPublishedVideoForm.js (1 hunks)
  • src/components/forms/video/VideoForm.js (1 hunks)
✅ Files skipped from review due to trivial changes (33)
  • components.json
  • src/components/content/carousels/skeletons/TemplateSkeleton.js
  • src/components/Layout.js
  • jsconfig.json
  • src/components/buttons/AlbyButton.js
  • src/components/content/lists/ContentListItem.js
  • src/components/content/carousels/CoursesCarousel.js
  • src/components/content/carousels/GenericCarousel.js
  • .prettierrc
  • src/components/content/SelectedContentItem.js
  • src/components/content/carousels/MessagesCarousel.js
  • src/components/content/lists/ProgressListItem.js
  • src/components/buttons/GenericButton.js
  • next.config.js
  • src/components/content/dropdowns/ContentDropdownItem.js
  • src/components/MoreInfo.js
  • src/components/forms/document/DocumentForm.js
  • src/components/content/carousels/VideosCarousel.js
  • src/components/feeds/messages/CommunityMessage.js
  • src/components/content/courses/VideoLesson.js
  • src/components/bitcoinConnect/BitcoinConnect.js
  • src/components/content/lists/PurchasedListItem.js
  • src/components/BottomBar.js
  • src/components/charts/CombinedContributionChart.js
  • src/components/charts/ActivityContributionChart.js
  • src/components/feeds/GlobalFeed.js
  • src/components/content/carousels/DocumentsCarousel.js
  • src/components/feeds/NostrFeed.js
  • src/components/bitcoinConnect/ResourcePaymentButton.js
  • src/components/forms/combined/CombinedResourceForm.js
  • src/components/content/courses/CombinedLesson.js
  • src/components/forms/video/VideoForm.js
  • src/components/bitcoinConnect/CoursePaymentButton.js
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/components/ZapThreadsWrapper.js (4)
src/components/profile/UserContent.js (1)
  • user (28-28)
src/pages/draft/[slug]/index.js (1)
  • user (32-32)
src/components/content/courses/DraftCourseDetails.js (1)
  • user (28-28)
src/pages/api/invoices/polling.js (1)
  • relays (111-111)
src/components/feeds/DiscordFeed.js (2)
src/hooks/useWindowWidth.js (1)
  • useWindowWidth (3-24)
src/components/feeds/messages/CommunityMessage.js (1)
  • CommunityMessage (64-199)
src/components/content/videos/VideoDetails.js (9)
src/components/bitcoinConnect/ResourcePaymentButton.js (6)
  • router (24-24)
  • session (22-22)
  • useToast (21-21)
  • windowWidth (25-25)
  • ResourcePaymentButton (18-144)
  • handlePaymentSuccess (70-96)
src/hooks/nostrQueries/zaps/useZapsSubscription.js (3)
  • useZapsSubscription (5-81)
  • zaps (6-6)
  • zapsLoading (7-7)
src/hooks/useWindowWidth.js (1)
  • useWindowWidth (3-24)
src/components/ui/MoreOptionsMenu.js (2)
  • menuRef (20-20)
  • MoreOptionsMenu (14-67)
src/hooks/useToast.js (1)
  • showToast (11-13)
src/utils/lightning.js (1)
  • total (9-9)
src/components/buttons/GenericButton.js (1)
  • GenericButton (4-35)
src/hooks/useImageProxy.js (1)
  • returnImageProxy (6-26)
src/components/ZapThreadsWrapper.js (1)
  • ZapThreadsWrapper (3-89)
src/components/content/lists/ContentList.js (2)
src/components/content/lists/ContentListItem.js (1)
  • ContentListItem (10-66)
src/components/profile/UserContent.js (1)
  • content (24-24)
🪛 Biome (1.9.4)
src/components/content/carousels/InteractivePromotionalCarousel.js

[error] 75-75: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 78-78: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CourseTemplate.js

[error] 132-141: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/documents/DocumentDetails.js

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 76-76: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 134-134: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 154-154: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/videos/VideoDetails.js

[error] 67-69: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 76-76: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 127-127: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 154-154: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/bitcoinConnect/SubscriptionPaymentButton.js

[error] 291-291: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/CombinedTemplate.js

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/DocumentTemplate.js

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/carousels/templates/VideoTemplate.js

[error] 34-34: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/combined/CombinedDetails.js

[error] 119-119: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseDetails.js

[error] 138-138: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 250-253: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/CourseLesson.js

[error] 61-61: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 171-172: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/DocumentLesson.js

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/DraftCourseDetails.js

[error] 241-241: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)


[error] 393-394: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/courses/DraftCourseLesson.js

[error] 35-36: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/content/lists/ContentList.js

[error] 2-2: Do not shadow the global "DataView" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

src/components/feeds/MessageInput.js

[error] 46-46: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/combined/EditDraftCombinedResourceForm.js

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 53-53: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 58-58: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/combined/EditPublishedCombinedResourceForm.js

[error] 31-31: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 144-144: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 151-151: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/course/CourseForm.js

[error] 21-21: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 92-92: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/course/LessonSelector.js

[error] 42-42: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/course/PublishedCourseForm.js

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/course/embedded/EmbeddedDocumentForm.js

[error] 50-50: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/course/embedded/EmbeddedVideoForm.js

[error] 38-38: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/document/EditDraftDocumentForm.js

[error] 19-19: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/document/EditPublishedDocumentForm.js

[error] 30-30: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/video/EditDraftVideoForm.js

[error] 20-20: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/video/EditPublishedVideoForm.js

[error] 31-31: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🪛 markdownlint-cli2 (0.17.2)
README.md

147-147: Bare URL used
null

(MD034, no-bare-urls)

🔇 Additional comments (69)
package.json (2)

10-10: Approve postinstall hook for Prisma client generation
Automatically generating the Prisma client after install streamlines the developer experience and ensures the schema is in sync.


11-11: Add lint:fix script for automatic code formatting
The new lint:fix script runs next lint --fix followed by Prettier on JS/TS/JSON/MD files, helping maintain a consistent code style.

src/components/content/carousels/templates/CombinedTemplate.js (1)

1-9: Approve import style improvements.
The imports have been reformatted consistently with single quotes and grouped logically, improving readability without altering functionality.

src/components/content/lists/ContentList.js (4)

1-1: Consistent import formatting
The switch to single quotes and the path alias (@/components/...) for ContentListItem aligns with the project’s style guidelines and enhances readability.

Also applies to: 3-3


5-7: Solid functional component structure and early exit
The arrow‐function signature for ContentList is concise, and the early null return when items is empty is an appropriate guard.


13-14: Approve JSX wrapper rendering
The <div className="grid grid-nogutter"> container cleanly lays out the list items in a grid without gutters.


19-19: Approve default export
Exporting ContentList as the default export matches existing module conventions in the codebase.

src/components/feeds/StackerNewsFeed.js (1)

8-95: Well-formatted code changes improve readability.

The code has been reformatted with consistent indentation and line breaks, which improves readability while maintaining the same functionality. The SVG component, function definitions, and JSX structure are now more consistent and easier to follow.

src/components/forms/course/PublishedCourseForm.js (1)

19-220: Code formatting improvements enhance readability.

The reformatting of this component with consistent indentation and line breaks improves the code's readability and maintainability while preserving the same functionality.

🧰 Tools
🪛 Biome (1.9.4)

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

README.md (1)

13-147: Documentation formatting improvements enhance readability.

The changes to the README add appropriate blank lines before section headings and improve the overall document structure, making it more readable.

🧰 Tools
🪛 LanguageTool

[grammar] ~50-~50: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...3 long-form events - Video: Formatted markdown documents with rich media support, incl...

(MARKDOWN_NNP)


[duplication] ~54-~54: Possible typo: you repeated a word.
Context: ...le with Lightning or through a PlebDevs subscription - Subscription options: - Pay-as-you-go: 50,000 sa...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~127-~127: Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short).
Context: ...up --build` Now the database is running and the migrations are applied. You can now...

(COMMA_COMPOUND_SENTENCE_2)

🪛 markdownlint-cli2 (0.17.2)

147-147: Bare URL used
null

(MD034, no-bare-urls)

src/components/forms/video/EditDraftVideoForm.js (1)

17-248: Improved code formatting enhances readability.

The reformatting of this component with consistent indentation and line breaks improves code readability and maintainability while preserving all functionality.

🧰 Tools
🪛 Biome (1.9.4)

[error] 20-20: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/feeds/DiscordFeed.js (2)

31-37: LGTM! Improved error handling.

The separation of error handling from normal rendering logic is a good improvement that makes the code more maintainable.


39-57:

Details

✅ Verification successful

Consider adding ZapThreads to DiscordFeed messages.

Other message components like CommunityMessage include ZapThreads functionality, but it's missing here. This might be intentional, but worth verifying for consistency with the PR's objectives.


🏁 Script executed:

#!/bin/bash
# Check if ZapThreadsWrapper is used in other message components
echo "Checking for ZapThreadsWrapper usage in other message components:"
rg -A 3 "ZapThreadsWrapper" src/components/feeds/messages/

Length of output: 1579


No missing ZapThreadsWrapper — already included in CommunityMessage

The DiscordFeed maps each message to the CommunityMessage component, which already imports and wraps its contents in ZapThreadsWrapper. No additional changes are required here.

src/components/banner/HeroBanner.js (1)

69-108: Excellent enhancement to user authentication flow.

The improved handling of anonymous credentials with localStorage persistence is a great addition. This will prevent repeated key generation and provide a consistent identity for ZapThreads functionality.

src/components/content/carousels/templates/VideoTemplate.js (1)

1-23: Approve import and style-only changes.
The reformatting of import statements to single quotes and grouping UI components improves consistency and readability without altering any functionality.

src/components/forms/course/embedded/EmbeddedDocumentForm.js (1)

1-256: Approve style-only reformatting.
Consistent indentation, single‑quote imports, and spacing enhancements maintain existing behavior while improving readability.

🧰 Tools
🪛 Biome (1.9.4)

[error] 50-50: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/feeds/MessageInput.js (1)

1-154: Approve style-only refactor.
The import normalization and adjusted indentation improve code readability without changing any functionality.

🧰 Tools
🪛 Biome (1.9.4)

[error] 46-46: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/components/forms/document/EditPublishedDocumentForm.js (1)

1-269: Approve consolidated error‑handling refactor.
The reformatted submission logic centralizes error handling, and the stylistic adjustments improve readability without altering the component’s behavior.

🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/forms/course/CourseForm.js (2)

19-25: State initialization looks clean and well-structured.

Your state initialization is clear and follows best practices with default values.

🧰 Tools
🪛 Biome (1.9.4)

[error] 21-21: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


71-123: The form submission logic looks good.

The course draft creation flow is well-structured with proper error handling and user feedback.

🧰 Tools
🪛 Biome (1.9.4)

[error] 92-92: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

src/components/content/carousels/InteractivePromotionalCarousel.js (3)

242-273: New Donate button added for PLEBDEVS category.

Good addition of the Donate button that integrates with the existing copyToClipboard function for Lightning donations, aligning with the PR's objective of enhancing content interaction features.


59-60: Clean definition of focus outline styles.

The yellow focus outline style is well-defined and will provide consistent styling across the application.


339-353: Clean implementation of promotion selection.

The promotion selection interface is clean and well-structured, with clear visual feedback for the selected item.

src/components/content/courses/DocumentLesson.js (3)

18-20: Clean import of MDDisplay component.

The dynamic import of the MDDisplay component is well-structured and includes the proper SSR setting.


43-93: Well-implemented menu item building logic.

The menu building function provides appropriate options based on user access level and handles the completion marking functionality well.

🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


119-139: Good content access control implementation.

The renderContent function effectively implements the access control for content visibility, showing appropriate UI states based on payment and decryption status. This aligns with the PR objective of refining access control for content.

src/components/forms/document/EditDraftDocumentForm.js (3)

31-35: User session handling looks good.

The useEffect hook correctly sets the user from the session and includes session in the dependency array.


41-74: Well-structured document update logic.

The form submission handler properly validates the user, constructs the payload with appropriate data transformations, and handles API responses with clear user feedback.


58-58: Nice implementation of topic normalization.

The line ensures topics are trimmed, lowercased, and deduplicated while always including the "document" tag, which helps with consistent categorization.

src/components/ZapThreadsWrapper.js (11)

3-3: Great addition of the isAuthorized prop for access control.

The addition of the isAuthorized prop aligns well with the PR objective of implementing refined access control for comments. This will enable conditional loading of ZapThreads based on user authorization status.


8-11: Early return pattern correctly prevents script loading for unauthorized users.

This is an excellent use of the early return pattern to prevent script loading and execution when the user isn't authorized, improving both performance and security.


26-29: Improved user attribute handling.

The conditional check before setting the user attribute prevents potential issues with null or undefined values.


31-37: Enhanced relay URL sanitization.

The improved relay URL handling with splitting, trimming, filtering, and rejoining ensures that only valid, clean relay URLs are used. This makes the component more robust when dealing with various input formats.


39-41: Consistent disable attribute handling.

Setting the disable attribute regardless of its value ensures consistent behavior across different scenarios.


42-46: Added error handling for ZapThreads element.

Good addition of error event listeners to catch and log errors from the ZapThreads element.


47-53: Improved cleanup for existing elements.

The enhanced cleanup logic to remove all children instead of just the first child ensures a clean state before appending new elements, preventing potential memory leaks and UI issues.


62-64: Added error handling for script loading.

Good addition of error event listeners for script loading failures, which will help with debugging if the ZapThreads script fails to load.


71-75: Comprehensive cleanup in useEffect.

The improved cleanup function ensures all child elements are properly removed when the component unmounts, preventing memory leaks.


81-81: Updated dependency array with isAuthorized.

Correctly added isAuthorized to the dependency array to ensure the effect runs when authorization status changes.


83-85: Null rendering for unauthorized users.

Returning null when the user is not authorized prevents unnecessary DOM elements from being rendered, improving performance and ensuring a clean DOM structure.

src/components/content/dropdowns/MessageDropdownItem.js (10)

1-11: Well-organized imports with proper hooks.

The imports are neatly organized, and the addition of useMemo and useCallback hooks indicates optimization for performance.


21-22: Optimal memoization of message reference.

Using useMemo to create a stable reference to the message is good practice to prevent unnecessary re-renders and infinite loops.


24-29: Improved platform detection with useCallback.

Memoizing the platform detection function with useCallback prevents unnecessary function recreations on each render.


31-49: Properly memoized Nostr author fetching.

The fetchNostrAuthor function is correctly memoized with useCallback and includes proper error handling and null checks.


52-55: Efficient prevention of redundant processing.

The early return in useEffect when no message reference exists or when message data is already loaded prevents unnecessary processing cycles.


143-143: Comprehensive dependency array in useEffect.

The dependency array includes all necessary dependencies, including the newly memoized functions, preventing infinite loops while ensuring the effect runs when needed.


145-156: Reusable platform icon retrieval with useCallback.

The getPlatformIcon function is correctly memoized with useCallback, improving performance for repeated calls.


159-207: Well-structured mobile view extraction.

Extracting the simplified mobile view as a separate function with proper memoization is a good architectural decision that improves code organization and maintainability.


209-216: Optimized message object creation with useMemo.

Creating a memoized finalMessage object prevents unnecessary recreations on each render, improving performance.


227-229: Responsive conditional rendering based on device.

The conditional rendering logic that switches between mobile and desktop views based on screen size provides a better user experience across devices.

src/components/forms/combined/EditDraftCombinedResourceForm.js (6)

16-16: Simplified dynamic import of MDEditor.

The dynamic import of MDEditor is now more concise and readable.


37-73: Well-structured video URL extraction logic.

The logic for extracting video URLs from different sources (YouTube, Vimeo, etc.) is clear and well-structured, with proper error handling and fallbacks.

🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 53-53: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 58-58: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


81-83: Good use of useCallback for content change handler.

Memoizing the content change handler with useCallback prevents unnecessary function recreations on each render.


85-118: Comprehensive video embed generation logic.

The getVideoEmbed function handles various video sources and formats, including conditional handling for paid vs. free resources.


120-158: Well-structured form submission handler.

The handleSubmit function follows a clear flow: fetch user data, create payload, make API request, handle response, and show appropriate notifications.


196-310: Clean and organized form structure.

The form layout is well-organized with clear sections for different input types, proper spacing, and consistent styling.

src/components/forms/combined/EditPublishedCombinedResourceForm.js (6)

18-20: Improved MDEditor import formatting.

The dynamic import of MDEditor is now more readable with proper line breaks and indentation.


41-57: Clear extraction of video embed and content.

The logic for separating video embed from content is well-structured with appropriate fallbacks.


65-71: Good use of useCallback for input handlers.

Memoizing both the videoEmbed and content change handlers is a good practice for performance optimization.


73-99: Well-organized helper functions for managing form arrays.

The functions for adding, removing, and updating links and topics are consistently structured and follow good practices.


101-189: Comprehensive form submission handler with proper error handling.

The handleSubmit function covers all necessary steps: checking signer presence, combining and potentially encrypting content, constructing the event with appropriate tags, validating, publishing, and updating the backend.

🧰 Tools
🪛 Biome (1.9.4)

[error] 144-144: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 151-151: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


191-323: Clean and consistent form structure.

The form layout is well-organized with clear separation between different sections, consistent indentation, and good use of React components.

src/components/bitcoinConnect/SubscriptionPaymentButton.js (7)

16-19: Approve dynamic import usage
The PaymentModal component is correctly lazy‐loaded with ssr: false, improving bundle size and avoiding SSR mismatches.


21-29: Component signature and default props look good
The prop names and default values (oneTime, recurring, layout) are clear and align with usage. No breaking changes introduced here.


30-35: State hooks and session retrieval are correct
Initialization of invoice, showRecurringOptions, nwcInput and destructuring of showToast, session, status, and router are idiomatic.


40-42: Initialization effect is concise
Calling initializeBitcoinConnect() on mount is simple and correct. No cleanup necessary here.


67-82: fetchInvoice robust error handling
The fetchInvoice function catches errors, reports via toast, and invokes onError. This is good practice. No further changes needed.


84-88: Payment success handler is sound
You track analytics, notify via toast, and bubble up via onSuccess. All side effects are well-managed.


90-94: Payment error handler is appropriate
Console logging plus user feedback and onError callback provide solid error handling.

@kiwihodl kiwihodl force-pushed the bgBlur2AndZapThreadsParent branch from 23c9d1f to bd135db Compare April 17, 2025 18:38
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/components/content/videos/VideoDetails.js (2)

55-57: Nit: co‑locate related state or derive on the fly

nsec and npub are mutually exclusive and always derived from session.
Keeping both in state invites drift. Consider storing a single nostrUserKey
value (string | null) computed inside the same useEffect, or memoising it
with useMemo instead of two separate useState calls.


176-184: Optional: simplify deeply nested optional access

The repeated look‑ups into session?.user?.purchased?.find(...)?
make the template hard to read and are prone to “cannot read property”
mistakes. Extract the purchase into a local variable:

const coursePurchase = session?.user?.purchased?.find(
  p => p.courseId === course
);
...
tooltip={`You have this lesson through purchasing the course it belongs to. You paid ${coursePurchase?.course?.price ?? '??'} sats for the course.`}
label={`Paid ${coursePurchase?.course?.price ?? '??'} sats`}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 23c9d1f and bd135db.

📒 Files selected for processing (12)
  • .prettierrc (1 hunks)
  • src/components/ZapThreadsWrapper.js (2 hunks)
  • src/components/content/carousels/templates/CourseTemplate.js (7 hunks)
  • src/components/content/combined/CombinedDetails.js (4 hunks)
  • src/components/content/courses/CourseDetails.js (3 hunks)
  • src/components/content/courses/CourseLesson.js (3 hunks)
  • src/components/content/documents/DocumentDetails.js (5 hunks)
  • src/components/content/videos/VideoDetails.js (5 hunks)
  • src/config/appConfig.js (1 hunks)
  • src/pages/api/lightning-address/callback/[slug].js (1 hunks)
  • src/pages/course/[slug]/index.js (5 hunks)
  • src/styles/globals.css (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/pages/api/lightning-address/callback/[slug].js
  • src/styles/globals.css
  • .prettierrc
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/config/appConfig.js
  • src/components/content/courses/CourseLesson.js
  • src/pages/course/[slug]/index.js
  • src/components/ZapThreadsWrapper.js
  • src/components/content/courses/CourseDetails.js
  • src/components/content/documents/DocumentDetails.js
  • src/components/content/combined/CombinedDetails.js
  • src/components/content/carousels/templates/CourseTemplate.js
🔇 Additional comments (2)
src/components/content/videos/VideoDetails.js (2)

46-49: Guard against zaps being undefined

useZapsSubscription is assumed to always return an array, yet the code later uses zaps.length.
If the hook returns undefined during its initial cycle, an exception will be thrown.

-  const { zaps, zapsLoading, zapsError } = useZapsSubscription({
-    event: processedEvent,
-  });
+  const {
+    zaps = [],          // safe default
+    zapsLoading,
+    zapsError,
+  } = useZapsSubscription({ event: processedEvent });

or add an optional‑chaining guard in the effect.
Please verify the hook’s contract and apply one of the safeguards.


330-349: Relay list is passed as a string – confirm ZapThreadsWrapper expects an array

appConfig.defaultRelayUrls.join(',') produces a single comma‑separated string,
yet many Nostr libraries expect an array of relay URLs.

-  relays={appConfig.defaultRelayUrls.join(',')}
+  relays={appConfig.defaultRelayUrls}

Please verify the prop type inside ZapThreadsWrapper.
Passing the wrong type will break relay connections silently.

In addition, the constant isAuthorized={true} can be replaced with a derived
boolean to avoid accidental discrepancies with the surrounding access logic.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/components/content/videos/VideoDetails.js (1)

146-153: ⚠️ Potential issue

Reset derived keys when session changes & avoid stale values

When the user signs out or switches accounts, nsec / npub should be cleared to prevent leaking the previous user's key into a new session.

Apply this diff to reset keys properly:

useEffect(() => {
  if (session?.user?.privkey) {
    const privkeyBuffer = Buffer.from(session.user.privkey, 'hex');
    setNsec(nip19.nsecEncode(privkeyBuffer));
+    setNpub(null);
  } else if (session?.user?.pubkey) {
+    setNsec(null);
    setNpub(nip19.npubEncode(session.user.pubkey));
+  } else {
+    setNsec(null);
+    setNpub(null);
  }
}, [session]);
🧹 Nitpick comments (1)
src/components/content/videos/VideoDetails.js (1)

332-351: Consider explicit authorization verification for ZapThreadsWrapper

The ZapThreads integration correctly enforces content access controls, but hardcoding isAuthorized={true} may be misleading since authorization is actually determined by the surrounding conditional.

Consider making the authorization check more explicit:

{nAddress && (
  <div className="mt-8">
    {!paidResource || decryptedContent || session?.user?.role?.subscribed ? (
      <ZapThreadsWrapper
        anchor={nAddress}
        user={session?.user ? nsec || npub : null}
        relays={appConfig.defaultRelayUrls.join(',')}
        disable="zaps"
-       isAuthorized={true}
+       isAuthorized={!paidResource || decryptedContent || session?.user?.role?.subscribed}
      />
    ) : (
      <div className="text-center p-4 bg-gray-800/50 rounded-lg">
        <p className="text-gray-400">
          Comments are only available to content purchasers, subscribers, and the content
          creator.
        </p>
      </div>
    )}
  </div>
)}

This makes it clearer that authorization depends on the same conditions used for conditional rendering.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d6a9252 and 2a16f1b.

📒 Files selected for processing (1)
  • src/components/content/videos/VideoDetails.js (5 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/content/videos/VideoDetails.js

[error] 18-18: Shouldn't redeclare 'React'. Consider to delete it or rename it.

'React' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useEffect'. Consider to delete it or rename it.

'useEffect' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (3)
src/components/content/videos/VideoDetails.js (3)

57-58: LGTM! Added state variables for Nostr keys

Adding these state variables is necessary for storing the encoded Nostr keys used by ZapThreadsWrapper.


179-186: LGTM! Improved string formatting

The use of template literals for string interpolation in the tooltip and label makes the code more readable and maintainable.


337-338:

Details

❓ Verification inconclusive

Handle potential security issue with userkeys

The user key (nsec/npub) is passed directly to ZapThreadsWrapper, but we should ensure we're not exposing private keys unnecessarily.

Verify that:

  1. The ZapThreadsWrapper component securely handles private keys (nsec)
  2. The relays configuration can be trusted with these keys

🏁 Script executed:

#!/bin/bash
# Check how ZapThreadsWrapper component handles keys
rg -A 10 "user(?:=|\s*:)" src/components/ZapThreadsWrapper.js

# Check what's in the appConfig for relays
rg -A 10 "defaultRelayUrls" src/config/appConfig.js

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Locate the ZapThreadsWrapper component file
wrapper=$(rg -l "ZapThreadsWrapper" -n src)
echo "ZapThreadsWrapper file: $wrapper"

# Inspect how it handles the `user` prop
rg -n "user" -C3 "$wrapper"

# Look for any handling of `privateKey` inside the wrapper
rg -n "privateKey" -C3 "$wrapper"

# Search for direct usage of `nsec` (private key) in the repo
rg -n "nsec" -C3 src

Length of output: 27179


#!/usr/bin/env bash
set -e

# Inspect the ZapThreadsWrapper implementation directly
file="src/components/ZapThreadsWrapper.js"

echo "----- First 200 lines of $file -----"
sed -n '1,200p' "$file"

echo
echo "----- Look for network calls (fetch/axios) in $file -----"
grep -nE "fetch\(|axios\." -C2 "$file" || echo "No fetch or axios usage found."

echo
echo "----- Check how `user` prop is used -----"
grep -n "user" -C3 "$file"

🏁 Script executed:

#!/bin/bash
# Inspect ZapThreadsWrapper implementation to see how `user` prop is handled
rg -n "export default function ZapThreadsWrapper" -C5 src/components/ZapThreadsWrapper.js

# Show code around where `user` prop is used
rg -n "props.user" -C5 src/components/ZapThreadsWrapper.js

# Look for any direct usage or exposure of the private key inside the component
rg -nE "nsec|privkey|privateKey" -C5 src/components/ZapThreadsWrapper.js

Length of output: 286


#!/usr/bin/env bash
set -e

# Print the first 200 lines of ZapThreadsWrapper to inspect how it uses the `user` prop
sed -n '1,200p' src/components/ZapThreadsWrapper.js

# Show any use of the `user` prop later in the file (lines 200–400)
sed -n '200,400p' src/components/ZapThreadsWrapper.js

Verify secure handling of private keys in ZapThreadsWrapper

I wasn’t able to locate the implementation details of ZapThreadsWrapper in the codebase to confirm how it handles the user prop (nsec/npub) or whether it exposes private keys to the relay endpoints. Please ensure:

  • ZapThreadsWrapper does not log, stringify, or leak the nsec value in any network requests or UI elements.
  • The component passes only what’s strictly required to the underlying Nostr client and does not persist the private key in any browser storage.
  • All configured relay URLs in appConfig.defaultRelayUrls are trusted and support secure WebSocket (wss) connections.

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

♻️ Duplicate comments (2)
src/components/content/videos/VideoDetails.js (2)

18-23: Remove duplicate React imports and keep the new ones

There's a duplicate import of React and its hooks on line 18, which is redundant as these are already imported at line 1. The additional imports for Buffer, ZapThreadsWrapper, appConfig, and nip19 are necessary for the new functionality.

-import React, { useEffect, useState, useRef } from 'react';
// Needed for nip19 encoding on the client
import { Buffer } from 'buffer';
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
import appConfig from '@/config/appConfig';
import { nip19 } from 'nostr-tools';
🧰 Tools
🪛 Biome (1.9.4)

[error] 18-18: Shouldn't redeclare 'React'. Consider to delete it or rename it.

'React' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useEffect'. Consider to delete it or rename it.

'useEffect' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)


146-153: Reset derived keys when session changes to prevent key leakage

The current implementation doesn't properly clear the keys when the session changes, which could lead to key leakage between different users if they switch accounts.

  useEffect(() => {
    if (session?.user?.privkey) {
      const privkeyBuffer = Buffer.from(session.user.privkey, 'hex');
      setNsec(nip19.nsecEncode(privkeyBuffer));
+     setNpub(null);
    } else if (session?.user?.pubkey) {
+     setNsec(null);
      setNpub(nip19.npubEncode(session.user.pubkey));
+   } else {
+     setNsec(null);
+     setNpub(null);
    }
  }, [session]);
🧹 Nitpick comments (1)
src/components/content/videos/VideoDetails.js (1)

67-72: Simplify conditional checks with optional chaining

The error handling in the handleDelete function could be simplified using optional chaining to improve readability and reduce nesting.

    } catch (error) {
-      if (
-        error.response &&
-        error.response.data &&
-        error.response.data.error.includes('Invalid `prisma.resource.delete()`')
-      ) {
+      if (error.response?.data?.error?.includes('Invalid `prisma.resource.delete()`')) {
        showToast(
          'error',
          'Error',
          'Resource cannot be deleted because it is part of a course, delete the course first.'
        );
-      } else if (error.response && error.response.data && error.response.data.error) {
+      } else if (error.response?.data?.error) {
        showToast('error', 'Error', error.response.data.error);
      } else {

Also applies to: 77-79

🧰 Tools
🪛 Biome (1.9.4)

[error] 69-71: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 96d9c0d and e692857.

📒 Files selected for processing (11)
  • .prettierrc (1 hunks)
  • src/components/ZapThreadsWrapper.js (2 hunks)
  • src/components/content/carousels/templates/CourseTemplate.js (7 hunks)
  • src/components/content/combined/CombinedDetails.js (4 hunks)
  • src/components/content/courses/CourseDetails.js (3 hunks)
  • src/components/content/courses/CourseLesson.js (3 hunks)
  • src/components/content/documents/DocumentDetails.js (5 hunks)
  • src/components/content/videos/VideoDetails.js (5 hunks)
  • src/config/appConfig.js (1 hunks)
  • src/pages/course/[slug]/index.js (5 hunks)
  • src/styles/globals.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • .prettierrc
  • src/config/appConfig.js
  • src/styles/globals.css
  • src/components/content/combined/CombinedDetails.js
  • src/components/content/documents/DocumentDetails.js
  • src/components/ZapThreadsWrapper.js
  • src/components/content/courses/CourseLesson.js
  • src/components/content/courses/CourseDetails.js
  • src/components/content/carousels/templates/CourseTemplate.js
  • src/pages/course/[slug]/index.js
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/content/videos/VideoDetails.js

[error] 18-18: Shouldn't redeclare 'React'. Consider to delete it or rename it.

'React' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useEffect'. Consider to delete it or rename it.

'useEffect' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (3)
src/components/content/videos/VideoDetails.js (3)

332-351: Great implementation of conditional ZapThreads rendering

The conditional rendering of the ZapThreadsWrapper component correctly implements the access control requirements, showing comments only to users who have purchased the content, are subscribers, or are the content creator.


48-50: Clean formatting of useZapsSubscription hook usage

The code has been refactored to use a cleaner multi-line format for the hook invocation, which improves readability.


179-185: Template string formatting improvement

The code now uses template literals for dynamic content in the tooltip and label, making the code more readable and maintainable.

kiwihodl and others added 7 commits April 20, 2025 16:14
- Add global CSS styles for PrimeReact Dialog component backdrop

- Apply 8px blur effect with semi-transparent black background

- Style dialog components with dark theme matching application design

- Affects all main modals including:

  - User badges and profile modals

  - Subscription and payment modals

  - Course and resource modals

  - Info and form modals
… to CourseDetails, CourseTemplate, CourseLesson - Block comments for unpaid users - Allow comments for free content and authorized paid users
Adding the buffer import.

useEffect(() => {
  if (session?.user?.privkey) {
    const privkeyBuffer = Buffer.from(session.user.privkey, 'hex');
    setNsec(nip19.nsecEncode(privkeyBuffer));
  } else if (session?.user?.pubkey) {
    setNpub(nip19.npubEncode(session.user.pubkey));
  }
}, [session]);

The line const privkeyBuffer = Buffer.from(session.user.privkey, 'hex'); is using the global Buffer object. In Node.js environments, Buffer is available globally, but in the browser (where Next.js client components run), it isn't.
As the code rabbit mentioned, Webpack 5 (which Next.js uses) no longer automatically polyfills Node.js globals like Buffer. So when this code runs in the browser, it will throw a ReferenceError because Buffer is undefined.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
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

♻️ Duplicate comments (3)
src/components/content/videos/VideoDetails.js (3)

60-84: Ensure server-side deletion authorization

The client-side deletion logic handles errors well, but there's a need to verify that the server-side endpoint properly validates ownership before deletion.

Verify that the server-side deletion endpoint in src/pages/api/resources/[slug].js checks that the requester is the resource owner before proceeding with deletion.

🧰 Tools
🪛 Biome (1.9.4)

[error] 69-71: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 78-78: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


18-23: ⚠️ Potential issue

Remove duplicate React imports

The React, useEffect, useState, and useRef imports on line 18 are duplicates of the imports on line 1, causing linting errors and pipeline failures.

Apply this diff to fix the duplicate imports:

-import React, { useEffect, useState, useRef } from 'react';
// Needed for nip19 encoding on the client
import { Buffer } from 'buffer';
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
import appConfig from '@/config/appConfig';
import { nip19 } from 'nostr-tools';
🧰 Tools
🪛 Biome (1.9.4)

[error] 18-18: Shouldn't redeclare 'React'. Consider to delete it or rename it.

'React' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useEffect'. Consider to delete it or rename it.

'useEffect' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)

🪛 GitHub Actions: Lint

[error] 18-18: ESLint: Parsing error: Identifier 'React' has already been declared.


146-153: 🛠️ Refactor suggestion

Reset derived keys when session changes to prevent key leakage

When a user signs out or switches accounts, both nsec and npub should be properly reset to prevent leaking private keys across sessions.

Apply this change to properly handle session transitions:

useEffect(() => {
  if (session?.user?.privkey) {
    const privkeyBuffer = Buffer.from(session.user.privkey, 'hex');
    setNsec(nip19.nsecEncode(privkeyBuffer));
+    setNpub(null);
  } else if (session?.user?.pubkey) {
+    setNsec(null);
    setNpub(nip19.npubEncode(session.user.pubkey));
+  } else {
+    setNsec(null);
+    setNpub(null);
  }
}, [session]);
🧹 Nitpick comments (1)
src/components/content/videos/VideoDetails.js (1)

67-72: Use optional chaining for nested properties

The code is using multiple conditions to check for the existence of nested properties, which can be simplified with optional chaining.

Apply these changes to simplify the error handling:

- if (
-   error.response &&
-   error.response.data &&
-   error.response.data.error.includes('Invalid `prisma.resource.delete()`')
- ) {
+ if (error.response?.data?.error?.includes('Invalid `prisma.resource.delete()`')) {

- } else if (error.response && error.response.data && error.response.data.error) {
+ } else if (error.response?.data?.error) {

Also applies to: 77-79

🧰 Tools
🪛 Biome (1.9.4)

[error] 69-71: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e692857 and 02caab3.

📒 Files selected for processing (11)
  • .prettierrc (1 hunks)
  • src/components/ZapThreadsWrapper.js (2 hunks)
  • src/components/content/carousels/templates/CourseTemplate.js (7 hunks)
  • src/components/content/combined/CombinedDetails.js (4 hunks)
  • src/components/content/courses/CourseDetails.js (3 hunks)
  • src/components/content/courses/CourseLesson.js (3 hunks)
  • src/components/content/documents/DocumentDetails.js (5 hunks)
  • src/components/content/videos/VideoDetails.js (5 hunks)
  • src/config/appConfig.js (1 hunks)
  • src/pages/course/[slug]/index.js (5 hunks)
  • src/styles/globals.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • .prettierrc
  • src/config/appConfig.js
  • src/styles/globals.css
  • src/components/content/combined/CombinedDetails.js
  • src/components/content/courses/CourseDetails.js
  • src/components/ZapThreadsWrapper.js
  • src/components/content/carousels/templates/CourseTemplate.js
  • src/components/content/documents/DocumentDetails.js
  • src/components/content/courses/CourseLesson.js
  • src/pages/course/[slug]/index.js
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/content/videos/VideoDetails.js

[error] 18-18: Shouldn't redeclare 'React'. Consider to delete it or rename it.

'React' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useEffect'. Consider to delete it or rename it.

'useEffect' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)

🪛 GitHub Actions: Lint
src/components/content/videos/VideoDetails.js

[error] 18-18: ESLint: Parsing error: Identifier 'React' has already been declared.

🔇 Additional comments (2)
src/components/content/videos/VideoDetails.js (2)

332-351: LGTM: Well-implemented conditional comment rendering

The implementation correctly restricts comments to authorized users (purchasers, subscribers, or the content creator) and provides a clear message when access is denied.


332-341:

Details

❓ Verification inconclusive

Verify proper conditional authorization for the ZapThreadsWrapper

The isAuthorized prop is always set to true, but it should likely match the conditional logic that determines whether to show the ZapThreadsWrapper or the access message.


🏁 Script executed:

#!/bin/bash
# Check how isAuthorized is used in ZapThreadsWrapper
rg -A 10 "isAuthorized" --include "*.js" src/components/ZapThreadsWrapper.js

Length of output: 518


#!/bin/bash
# Locate the ZapThreadsWrapper implementation and inspect isAuthorized usage
echo "=== Files containing ZapThreadsWrapper ==="
rg -l "ZapThreadsWrapper" src

echo ""
echo "=== Occurrences of isAuthorized ==="
rg -n "isAuthorized" src

#!/bin/bash
# 1. Locate the ZapThreadsWrapper component file
echo "=== Locating ZapThreadsWrapper file ==="
file=$(fd ZapThreadsWrapper.js src | head -n 1)
if [[ -z "$file" ]]; then
  echo "ZapThreadsWrapper.js not found via fd, falling back to ripgrep."
  file=$(rg -l "ZapThreadsWrapper" src | head -n 1)
fi
echo "Found: $file"

# 2. Inspect occurrences of isAuthorized in that file
echo ""
echo "=== isAuthorized usage in $file ==="
rg -n "isAuthorized" -A 5 "$file"

#!/bin/bash
# Locate the ZapThreadsWrapper component file
file=$(find src -type f -name "ZapThreadsWrapper.js" | head -n 1)
echo "✔️ ZapThreadsWrapper file: $file"
echo ""

if [[ -f "$file" ]]; then
  echo "🔍 Searching for 'isAuthorized' in $file"
  rg -n "isAuthorized" -A5 "$file"
else
  echo "⚠️ ZapThreadsWrapper.js not found, listing all occurrences of ZapThreadsWrapper"
  rg -n "ZapThreadsWrapper" -A5 src
  echo ""
  echo "🔍 Searching globally for 'isAuthorized'"
  rg -n "isAuthorized" -A5 src
fi

Verify isAuthorized Prop Logic in ZapThreadsWrapper

It looks like in src/components/content/videos/VideoDetails.js (lines 332–341) you’re always passing isAuthorized={true} to ZapThreadsWrapper, regardless of whether the user actually meets the access conditions (!paidResource || decryptedContent || session?.user?.role?.subscribed). Please confirm that:

  • ZapThreadsWrapper internally respects the prop and gates functionality based on isAuthorized, or
  • You adjust the prop to reflect the same conditional used for rendering (e.g.
    isAuthorized={!paidResource || decryptedContent || session?.user?.role?.subscribed}).

Without inspecting the wrapper’s implementation, I can’t verify that the prop is doing anything, so please double‑check and ensure the authorization logic isn’t accidentally bypassed.

AustinKelsay and others added 2 commits April 20, 2025 16:30
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@AustinKelsay
Copy link
Copy Markdown
Owner

@CodeRabbit full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2025

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/content/videos/VideoDetails.js (1)

184-191: Consider more robust template string handling.

The template strings for the tooltip and label contain deeply nested optional chaining that could be simplified with a helper variable for better readability and maintainability.

Consider refactoring to:

-  tooltip={`You have this lesson through purchasing the course it belongs to. You paid ${
-    session?.user?.purchased?.find(purchase => purchase.courseId === course)?.course?.price
-  } sats for the course.`}
-  icon="pi pi-check"
-  label={`Paid ${
-    session?.user?.purchased?.find(purchase => purchase.courseId === course)?.course?.price
-  } sats`}
+  const coursePrice = session?.user?.purchased?.find(purchase => purchase.courseId === course)?.course?.price;
+  tooltip={`You have this lesson through purchasing the course it belongs to. You paid ${coursePrice} sats for the course.`}
+  icon="pi pi-check"
+  label={`Paid ${coursePrice} sats`}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f0e480 and 3f00091.

📒 Files selected for processing (1)
  • src/components/content/videos/VideoDetails.js (5 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/content/videos/VideoDetails.js

[error] 22-22: Shouldn't redeclare 'Buffer'. Consider to delete it or rename it.

'Buffer' is defined here:

(lint/suspicious/noRedeclare)

🪛 GitHub Actions: Lint
src/components/content/videos/VideoDetails.js

[error] 22-22: ESLint: Parsing error: Identifier 'Buffer' has already been declared. (22:9)

🔇 Additional comments (4)
src/components/content/videos/VideoDetails.js (4)

146-158: Proper key management implementation looks good.

The implementation correctly handles NIP-19 key encoding and ensures proper state management by:

  1. Prioritizing private key (nsec) when available
  2. Falling back to public key (npub) otherwise
  3. Ensuring mutual exclusivity by nullifying one key when setting the other
  4. Clearing both keys when neither is available

This addresses previous concerns about key management during session changes.


337-356: Well-implemented authorization for ZapThreads.

The conditional rendering of ZapThreadsWrapper implements proper access control as specified in the PR objectives:

  • Comments are hidden for paid content before payment
  • Comments are visible after payment, to subscribers, and to content creators
  • Clear messaging is provided when access is restricted

This implementation aligns well with the PR objectives for comment access control.


48-50: Clean formatting improvement.

The formatting changes to the useZapsSubscription hook call improve code readability and maintain consistency with the codebase style.


343-344:

Details

✅ Verification successful

Ensure relay URLs are properly handled.

While the implementation looks good, verify that the array-to-string conversion for relays will work as expected across all Nostr clients.


🏁 Script executed:

#!/bin/bash
# Check if ZapThreadsWrapper handles relay URLs consistently across the codebase
rg -A 2 "relays=" --glob="*.js" --glob="*.jsx" --glob="*.tsx"

Length of output: 2863


🏁 Script executed:

#!/bin/bash
# Locate definition of defaultRelayUrls in appConfig
rg -A 5 -n "defaultRelayUrls"

Length of output: 12791


🏁 Script executed:

#!/bin/bash
# Display lines around the relays prop in VideoDetails.js to identify the wrapper component
sed -n '320,360p' src/components/content/videos/VideoDetails.js

Length of output: 1487


🏁 Script executed:

#!/bin/bash
# Locate the ZapThreadsWrapper component implementation and display its beginning
file=$(rg -l "ZapThreadsWrapper" --glob="*.js" --glob="*.jsx" | head -n1)
echo "Found in: $file"
sed -n '1,200p' "$file"

Length of output: 6279


🏁 Script executed:

#!/bin/bash
# Find the ZapThreadsWrapper component implementation in src/components
file=$(rg -l "ZapThreadsWrapper" src/components --glob="*.js" --glob="*.jsx" | grep -v "pages" | head -n1)
echo "Found in: $file"
sed -n '1,200p' "$file"

Length of output: 3382


Relay URL handling verified and correct
ZapThreadsWrapper’s logic splits, trims, and re‑joins the comma‑separated string, so using appConfig.defaultRelayUrls.join(',') produces a properly formatted relays attribute. No issues detected; no changes necessary.

@AustinKelsay AustinKelsay merged commit 387c93f into main Apr 24, 2025
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request May 12, 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.

Blur bg on dropdown & modals Add ZapThreads comments to Courses and Course Lessons

2 participants