summaryrefslogtreecommitdiff
path: root/apps/web/app/reader/shares/_components
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-10 01:08:11 -0800
committerFuwn <[email protected]>2026-02-10 01:08:11 -0800
commit920d22332069f1ca60740c290173a95846fb38c3 (patch)
tree5909cecde94bf1acd83385a5b3d789175f2713f0 /apps/web/app/reader/shares/_components
parentfix: service worker cross-origin image handling and CI env vars (diff)
downloadasa.news-920d22332069f1ca60740c290173a95846fb38c3.tar.xz
asa.news-920d22332069f1ca60740c290173a95846fb38c3.zip
feat: scoped mark-all-read, share enhancements, notification z-index
- Mark all as read now scopes to current feed/folder instead of all - Added undo button to mark-all-read toast notification - Share notes can be toggled between public and private visibility - Track share view count and display in shares list - Activity-based share expiry: views reset the expiry timer - Fixed notification panel z-index layering behind content area
Diffstat (limited to 'apps/web/app/reader/shares/_components')
-rw-r--r--apps/web/app/reader/shares/_components/shares-content.tsx63
1 files changed, 56 insertions, 7 deletions
diff --git a/apps/web/app/reader/shares/_components/shares-content.tsx b/apps/web/app/reader/shares/_components/shares-content.tsx
index db50bc7..8012146 100644
--- a/apps/web/app/reader/shares/_components/shares-content.tsx
+++ b/apps/web/app/reader/shares/_components/shares-content.tsx
@@ -18,6 +18,9 @@ interface SharedEntry {
createdAt: string
expiresAt: string | null
note: string | null
+ noteIsPublic: boolean
+ viewCount: number
+ lastViewedAt: string | null
entryTitle: string | null
entryUrl: string | null
}
@@ -30,7 +33,7 @@ function useSharedEntries() {
queryFn: async () => {
const { data, error } = await supabaseClient
.from("shared_entries")
- .select("id, entry_id, share_token, created_at, expires_at, note, entries(title, url)")
+ .select("id, entry_id, share_token, created_at, expires_at, note, note_is_public, view_count, last_viewed_at, entries(title, url)")
.order("created_at", { ascending: false })
if (error) throw error
@@ -42,13 +45,18 @@ function useSharedEntries() {
url: string | null
} | null
+ const rowData = row as Record<string, unknown>
+
return {
identifier: row.id,
entryIdentifier: row.entry_id,
shareToken: row.share_token,
createdAt: row.created_at,
expiresAt: row.expires_at,
- note: (row as Record<string, unknown>).note as string | null,
+ note: rowData.note as string | null,
+ noteIsPublic: (rowData.note_is_public as boolean) ?? false,
+ viewCount: (rowData.view_count as number) ?? 0,
+ lastViewedAt: rowData.last_viewed_at as string | null,
entryTitle: entryData?.title ?? null,
entryUrl: entryData?.url ?? null,
}
@@ -85,20 +93,23 @@ function useUpdateShareNote() {
const queryClient = useQueryClient()
return useMutation({
- mutationFn: async ({ shareToken, note }: { shareToken: string; note: string | null }) => {
+ mutationFn: async ({ shareToken, note, noteIsPublic }: { shareToken: string; note?: string | null; noteIsPublic?: boolean }) => {
+ const payload: Record<string, unknown> = {}
+ if (note !== undefined) payload.note = note
+ if (noteIsPublic !== undefined) payload.noteIsPublic = noteIsPublic
const response = await fetch(`/api/share/${shareToken}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ note }),
+ body: JSON.stringify(payload),
})
- if (!response.ok) throw new Error("failed to update note")
+ if (!response.ok) throw new Error("failed to update share")
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["shared-entries"] })
- notify("note updated")
+ notify("share updated")
},
onError: () => {
- notify("failed to update note")
+ notify("failed to update share")
},
})
}
@@ -168,6 +179,11 @@ function ShareRow({
{share.entryTitle ?? "untitled"}
</span>
{expired && <span className="shrink-0 text-status-error">expired</span>}
+ {share.viewCount > 0 && (
+ <span className="shrink-0 text-text-dim">
+ {share.viewCount} view{share.viewCount !== 1 ? "s" : ""}
+ </span>
+ )}
<span className="shrink-0 text-text-dim">{sharedDate}</span>
</div>
</div>
@@ -204,6 +220,12 @@ function ShareRow({
)}
</>
)}
+ {share.viewCount > 0 && (
+ <>
+ <span>&middot;</span>
+ <span>{share.viewCount} view{share.viewCount !== 1 ? "s" : ""}</span>
+ </>
+ )}
{share.note && (
<>
<span>&middot;</span>
@@ -281,6 +303,18 @@ function ShareRow({
)}
</>
)}
+ {share.viewCount > 0 && (
+ <>
+ {" \u00b7 "}
+ {share.viewCount} view{share.viewCount !== 1 ? "s" : ""}
+ </>
+ )}
+ {share.noteIsPublic && share.note && (
+ <>
+ {" \u00b7 "}
+ <span>note is public</span>
+ </>
+ )}
</p>
</div>
</div>
@@ -304,6 +338,21 @@ function ShareRow({
>
{share.note ? "edit note" : "add note"}
</button>
+ {share.note && (
+ <button
+ type="button"
+ onClick={(event) => {
+ event.stopPropagation()
+ updateNote.mutate({
+ shareToken: share.shareToken,
+ noteIsPublic: !share.noteIsPublic,
+ })
+ }}
+ className="px-2 py-1 text-text-dim transition-colors hover:text-text-secondary"
+ >
+ {share.noteIsPublic ? "make note private" : "make note public"}
+ </button>
+ )}
{showRevokeConfirm ? (
<div className="flex items-center gap-1">
<span className="text-text-dim">revoke?</span>