diff options
| author | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
| commit | 5c5b1993edd890a80870ee05607ac5f088191d4e (patch) | |
| tree | a721b76bcd49ba10826c53efc87302c7a689512f /apps/web/app/reader/_components/highlight-popover.tsx | |
| download | asa.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.tsx | 96 |
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> + ) +} |