diff options
| author | Fuwn <[email protected]> | 2026-02-10 01:08:11 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-10 01:08:11 -0800 |
| commit | 920d22332069f1ca60740c290173a95846fb38c3 (patch) | |
| tree | 5909cecde94bf1acd83385a5b3d789175f2713f0 /apps/web/app/reader/shares/_components | |
| parent | fix: service worker cross-origin image handling and CI env vars (diff) | |
| download | asa.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.tsx | 63 |
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>·</span> + <span>{share.viewCount} view{share.viewCount !== 1 ? "s" : ""}</span> + </> + )} {share.note && ( <> <span>·</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> |