summaryrefslogtreecommitdiff
path: root/apps/web/app/reader/_components/highlight-popover.tsx
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-07 01:42:57 -0800
committerFuwn <[email protected]>2026-02-07 01:42:57 -0800
commit5c5b1993edd890a80870ee05607ac5f088191d4e (patch)
treea721b76bcd49ba10826c53efc87302c7a689512f /apps/web/app/reader/_components/highlight-popover.tsx
downloadasa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz
asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker. Includes three subscription tiers (free/pro/developer), API key auth, read-only REST API, webhook push notifications, Stripe billing with proration, and PWA support.
Diffstat (limited to 'apps/web/app/reader/_components/highlight-popover.tsx')
-rw-r--r--apps/web/app/reader/_components/highlight-popover.tsx96
1 files changed, 96 insertions, 0 deletions
diff --git a/apps/web/app/reader/_components/highlight-popover.tsx b/apps/web/app/reader/_components/highlight-popover.tsx
new file mode 100644
index 0000000..301c174
--- /dev/null
+++ b/apps/web/app/reader/_components/highlight-popover.tsx
@@ -0,0 +1,96 @@
+"use client"
+
+import { useState } from "react"
+
+interface HighlightPopoverProperties {
+ highlightIdentifier: string
+ note: string | null
+ anchorRect: DOMRect
+ containerRect: DOMRect
+ onUpdateNote: (note: string | null) => void
+ onDelete: () => void
+ onDismiss: () => void
+}
+
+export function HighlightPopover({
+ note,
+ anchorRect,
+ onUpdateNote,
+ onDelete,
+ onDismiss,
+}: HighlightPopoverProperties) {
+ const [isEditingNote, setIsEditingNote] = useState(false)
+ const [editedNoteText, setEditedNoteText] = useState(note ?? "")
+
+ const popoverLeft = anchorRect.left + anchorRect.width / 2
+ const popoverTop = anchorRect.bottom + 4
+
+ function handleSaveNote() {
+ onUpdateNote(editedNoteText.trim() || null)
+ setIsEditingNote(false)
+ }
+
+ return (
+ <div
+ className="fixed z-[100] -translate-x-1/2"
+ style={{ left: popoverLeft, top: popoverTop }}
+ >
+ <div className="min-w-48 border border-border bg-background-secondary p-2">
+ {isEditingNote ? (
+ <div className="space-y-1">
+ <input
+ type="text"
+ value={editedNoteText}
+ onChange={(event) => setEditedNoteText(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") handleSaveNote()
+ if (event.key === "Escape") onDismiss()
+ }}
+ placeholder="add a note..."
+ className="w-full border border-border bg-background-primary px-2 py-1 text-xs text-text-primary outline-none"
+ autoFocus
+ />
+ <div className="flex gap-1">
+ <button
+ type="button"
+ onClick={handleSaveNote}
+ className="px-2 py-1 text-xs text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
+ >
+ save
+ </button>
+ <button
+ type="button"
+ onClick={() => setIsEditingNote(false)}
+ className="px-2 py-1 text-xs text-text-dim transition-colors hover:text-text-secondary"
+ >
+ cancel
+ </button>
+ </div>
+ </div>
+ ) : (
+ <div className="space-y-1">
+ {note && (
+ <p className="text-xs text-text-secondary">{note}</p>
+ )}
+ <div className="flex gap-1">
+ <button
+ type="button"
+ onClick={() => setIsEditingNote(true)}
+ className="px-2 py-1 text-xs text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
+ >
+ {note ? "edit note" : "add note"}
+ </button>
+ <button
+ type="button"
+ onClick={onDelete}
+ className="px-2 py-1 text-xs text-status-error transition-colors hover:bg-background-tertiary"
+ >
+ remove
+ </button>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ )
+}