diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2f95bb..3ec97478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **First-time sign-in no longer breaks at the set-password step** — after verifying a magic code, a brand-new user (one who hasn't set a password yet) was advanced to the "set password" screen but the tokens issued by `verify-magic-code` were discarded, so the follow-up `POST /auth/set-password` (which requires an authenticated user) returned 401 and bounced the user back to the login screen — never able to finish onboarding. The tokens are now persisted before the set-password step. The password-login form also validates the email format client-side, so a malformed address shows a friendly "Enter a valid email address" instead of surfacing the raw backend validation message. - **"Copy invite link" works over plain HTTP / LAN** — the admin users page called the browser Clipboard API directly, which only exists in a secure context (HTTPS or `localhost`); on a plain-HTTP LAN address the button threw. It now uses the shared clipboard helper (with an `execCommand` fallback) and only shows "Copied" on success. - **Presigned URLs are correct in AWS S3 mode** — with `S3_STORAGE=s3`, a configured `S3_PUBLIC_ENDPOINT` (a MinIO/dev-only concept) would override the AWS host and point presigned upload/download URLs at the wrong endpoint. AWS mode now always uses native presigned URLs and ignores `S3_PUBLIC_ENDPOINT`. -- **Public share links open on the media, not the comment panel, on phones** — the comment panel in both share viewers (single asset and folder) defaulted to open at every screen size, so opening a share link on a phone showed the comment pane first with the media squeezed to a sliver off-screen. The panel now starts closed below the `md` breakpoint (768px) and open above it; it can still be toggled either way. +- **Public share links are usable on phones** — the comment panel defaulted to open at every screen size and was a fixed 360px column that refused to shrink, so opening a share link on a phone showed the comment pane first with the media squeezed to a sliver — and tapping the toggle to leave a comment did it again. Below the `md` breakpoint (768px) the panel now starts closed and opens as a full-width sheet over the media instead of competing for width; at `md` and above the side-by-side layout is unchanged. Applies to both the single-asset and folder share viewers. ## [1.6.0] - 2026-07-14 diff --git a/apps/web/app/share/[token]/page.tsx b/apps/web/app/share/[token]/page.tsx index 41c92ca3..c5b6218e 100644 --- a/apps/web/app/share/[token]/page.tsx +++ b/apps/web/app/share/[token]/page.tsx @@ -693,7 +693,7 @@ function ShareRightPanel({ const [activeTab, setActiveTab] = React.useState<'comments' | 'fields'>('comments') return ( -
+
{/* Tabs */}
@@ -874,7 +874,7 @@ function ShareViewer({ /> {/* Main content: viewer + sidebar */} -
+
{/* Left: full-screen media viewer */} void -} - -function FolderAssetViewer({ - token, - assetId, - permission, - allowDownload, - branding, - folderName, - onBack, -}: FolderAssetViewerProps) { - const [streamUrl, setStreamUrl] = React.useState(null) - const [thumbnailUrl, setThumbnailUrl] = React.useState(null) - const [assetInfo, setAssetInfo] = React.useState<{ - name: string - asset_type: string - description?: string - status?: string - keywords?: string[] - } | null>(null) - const [loading, setLoading] = React.useState(true) - - React.useEffect(() => { - let cancelled = false - setLoading(true) - - const streamPromise = fetch(`${API_URL}/share/${token}/stream/${assetId}`) - .then((r) => (r.ok ? r.json() : null)) - .catch(() => null) - - const thumbPromise = fetch(`${API_URL}/share/${token}/thumbnail/${assetId}`) - .then((r) => { - if (!r.ok) return null - const contentType = r.headers.get('content-type') - if (contentType?.includes('application/json')) { - return r.json() - } - return { url: `${API_URL}/share/${token}/thumbnail/${assetId}` } - }) - .catch(() => null) - - Promise.all([streamPromise, thumbPromise]).then(([streamData, thumbData]) => { - if (cancelled) return - if (streamData?.url) setStreamUrl(streamData.url) - if (streamData?.name) - setAssetInfo({ - name: streamData.name, - asset_type: streamData.asset_type ?? 'image', - description: streamData.description, - status: streamData.status, - keywords: streamData.keywords, - }) - else setAssetInfo({ name: 'Asset', asset_type: 'image' }) - if (thumbData?.url) setThumbnailUrl(thumbData.url) - setLoading(false) - }) - - return () => { - cancelled = true - } - }, [token, assetId]) - - if (loading) { - return ( -
- -
- ) - } - - const assetType = assetInfo?.asset_type ?? 'image' - - // Build a pseudo-asset object for the viewer - const pseudoAsset = { - id: assetId, - name: assetInfo?.name ?? 'Asset', - asset_type: assetType, - description: assetInfo?.description, - status: assetInfo?.status ?? 'draft', - keywords: assetInfo?.keywords ?? [], - thumbnail_url: thumbnailUrl, - stream_url: streamUrl, - } as Asset & { thumbnail_url?: string; stream_url?: string } - - return ( - - ) -} // ─── Page ───────────────────────────────────────────────────────────────────── diff --git a/apps/web/components/share/folder-share-viewer.tsx b/apps/web/components/share/folder-share-viewer.tsx index 86890772..23b00f89 100644 --- a/apps/web/components/share/folder-share-viewer.tsx +++ b/apps/web/components/share/folder-share-viewer.tsx @@ -882,7 +882,7 @@ function ShareReviewInner({
{/* Main: viewer + sidebar */} -
+
{/* Media viewer — reuses project components */}
{asset.asset_type === 'video' && versionReady && VideoPlayer ? ( @@ -922,7 +922,7 @@ function ShareReviewInner({ {/* Right sidebar — reuses project comment panel */} {sidebarOpen && ( -
+