Allow viewing shared documents on share and tracked link pages - #45
Allow viewing shared documents on share and tracked link pages#45zmeyer44 wants to merge 5 commits into
Conversation
Recipients of tracked links (including View Only) and share links could only see the file name and size — there was no way to open the document. View-only links had no server path to the file at all since getDownloadUrl rejects access !== "download". - Add public getPreviewUrl procedures to the trackedLinks and shares routers: same validation as getDownloadUrl, but allowed for view-only access and not counted against download limits - Add SharedFilePreview component reusing the in-app PreviewArea - Make file cards and folder file rows clickable on /t/[token] and /shared/[token], opening an inline preview with Back and (for download links) Download actions - Make onDownload optional in PreviewArea/UnsupportedPreview so view-only links don't offer a download button Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds inline document previews to public share and tracked-link pages and extends the existing viewer with Excel support.
Confidence Score: 2/5The PR does not appear safe to merge until tracked-link previews enforce maximum-view restrictions and failed text preview requests surface as errors. The preview endpoint still returns complete signed file URLs without consuming the tracked link's view allowance, and the shared text-preview path still renders ordinary HTTP error responses as document content. Files Needing Attention: apps/web/server/trpc/routers/tracked-links.ts; apps/web/features/files/shared-preview/index.tsx
|
| Filename | Overview |
|---|---|
| apps/web/server/trpc/routers/tracked-links.ts | Adds a public preview URL procedure for tracked files while retaining an unresolved maximum-view enforcement issue. |
| apps/web/server/trpc/routers/shares.ts | Adds a scoped public preview URL procedure for individual and folder-shared files. |
| apps/web/features/files/shared-preview/index.tsx | Adds the shared preview orchestrator, but its text fetch still treats non-success HTTP bodies as document content. |
| apps/web/components/xlsx-viewer.tsx | Adds client-side workbook parsing, sheet selection, and CSV-table rendering. |
| apps/web/app/t/[token]/page.tsx | Adds inline previews to tracked-link file cards and folder rows with download controls based on access mode. |
| apps/web/app/shared/[token]/page.tsx | Adds inline previews and navigation for directly shared files and files within shared folders. |
Sequence Diagram
sequenceDiagram
participant R as Recipient
participant P as Share/Tracked Page
participant T as tRPC Preview Procedure
participant S as Storage
participant V as PreviewArea
R->>P: Select shared file
P->>T: Request preview URL
T->>T: Validate token and file scope
T->>S: Create signed URL
S-->>T: Signed file URL
T-->>P: Preview URL
P->>V: Render URL or fetched text
V->>S: Fetch file bytes
S-->>V: Document content
Reviews (5): Last reviewed commit: "better duration format" | Re-trigger Greptile
| if (link.validUntil && new Date(link.validUntil) < new Date()) { | ||
| throw new Error("Link is no longer active"); | ||
| } | ||
| if (link.maxViews && link.viewCount >= link.maxViews) { |
There was a problem hiding this comment.
Preview bypasses maximum views
When a valid tracked link remains below maxViews, getPreviewUrl returns complete one-hour signed file URLs without incrementing viewCount, so a recipient can repeatedly retrieve the document without consuming the configured allowance.
How this was verified: The public mutation checks viewCount, returns a signed URL, and contains no counter update, while only the separate access procedure increments the counter.
Knowledge Base Used: Uploads, Share Links, Upload Links, and Tracked Links
| ) { | ||
| throw new Error("Incorrect password"); | ||
| } | ||
| if (link.maxDownloads && link.downloadCount >= link.maxDownloads) { |
There was a problem hiding this comment.
Preview bypasses download limits
When a share has maxDownloads, getPreviewUrl returns complete one-hour signed file URLs without incrementing downloadCount, so a recipient can retrieve the file repeatedly without consuming the configured limit.
How this was verified: The preview mutation only reads downloadCount, whereas both existing byte-serving paths conditionally increment it before returning access to the file.
Knowledge Base Used: Uploads, Share Links, Upload Links, and Tracked Links
|
|
||
| const vt = getViewerType(file.mimeType, file.name); | ||
| if (vt === "text" || vt === "markdown" || vt === "csv" || vt === "html") { | ||
| const text = await fetch(url).then((r) => r.text()); |
There was a problem hiding this comment.
Error responses become preview content
For text-like files, fetch(url).then((r) => r.text()) accepts HTTP error responses as document content, so storage errors such as 403, 404, or 410 responses are rendered or parsed as the shared file instead of entering the component's error state.
Knowledge Base Used: File Explorer, File Viewer, and Virtual Filesystem
Excel files previously fell through to the unsupported-preview state. Add a spreadsheet viewer using SheetJS (installed from the SheetJS CDN tarball, since the npm-registry xlsx package is stale and has known vulnerabilities), which handles both legacy .xls and modern .xlsx. The viewer converts the active sheet to CSV and reuses the existing CsvPreview table (search, sort, pagination), with a tab bar for multi-sheet workbooks. The library is dynamically imported client-side only, matching the docx/pdf viewer pattern. Works in the in-app file viewer and on share/tracked link pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Landscape PDFs overflowed horizontally on initial load. Two bugs: - The container-width ResizeObserver attached in a mount-only effect, but on mount the loading skeleton is rendered and the scroll container doesn't exist yet, so containerWidth stayed 0 and PDFPage skipped its fit-to-width clamp entirely, rendering wide pages at natural size. The effect now re-attaches when loading completes. - With the width actually measured, the auto-scale-on-load effect and the fit-width/fit-page handlers double-applied the fit factor (scale was set to fitScale and PDFPage multiplied by min(fitScale, 1) again, yielding fitScale^2). Removed the redundant auto-scale effect — the per-page clamp already makes scale 1 fit the full width — and divided the clamp back out in the fit handlers so they hit exact targets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The share-page card is vertically centered and the preview area had no fixed height, so the page jumped as the loader was swapped for the rendered document. Give SharedFilePreview a fixed-height container and force the preview roots to fill it (same [&>div]:h-full pattern the in-app viewer uses), so the card height is stable from first paint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
When a recipient opens a tracked link created with View Only, they see only the file name and size — the document card isn't clickable and there is no way to view the document. This was true for both view-only and download links on
/t/[token], and for regular share links on/shared/[token].Root cause was twofold:
divwith no click handler.trackedLinks.getDownloadUrl) hard-rejectsaccess !== "download".Changes
Server
getPreviewUrlmutation on thetrackedLinksrouter — same validation asgetDownloadUrl(active, expiry/validity window, max views, password, email, workspace match, folder containment) but allowed for bothviewanddownloadaccess, and it does not increment the download counter.getPreviewUrlon thesharesrouter — respects themaxDownloadscap but previewing doesn't consume a download.Client
SharedFilePreviewcomponent (features/files/shared-preview) that fetches the signed URL and renders the existingPreviewArea(PDF, DOCX, images, video, audio, markdown, CSV, HTML, text)./t/[token]and/shared/[token]: the file card is now a clickable button, and file rows inside shared folders are clickable too. Clicking opens the rendered document inline, with a Back button and — only when the link allows downloads — a Download button. The card widens while previewing.PreviewArea/UnsupportedPreview:onDownloadis now optional, so view-only links show "Preview is not available for this file type" without offering a download button.Notes
download-all-shared-folder(Add download all button to shared folder page #44 area) also touchesapps/web/app/shared/[token]/page.tsx, so a small merge conflict is expected in the file-row/download-handler area whichever lands second.Testing
pnpm typecheck/pnpm lint(tsc) pass inapps/web.🤖 Generated with Claude Code
Update: Excel preview support
Second commit adds viewing for Excel files, which previously hit the "unsupported" state:
XlsxViewerpowered by SheetJS (installed from the SheetJS CDN tarball — the npm-registryxlsxpackage is stale at 0.18.5 with known vulnerabilities). Handles both legacy.xlsand modern.xlsx.CsvPreviewtable (search, sort, pagination), with a tab bar for multi-sheet workbooks.