From 46cbb80df84fb32067d264f30b75b63edb02d996 Mon Sep 17 00:00:00 2001 From: Vrinda Kumar Date: Mon, 20 Jul 2026 13:28:19 +0530 Subject: [PATCH 1/2] fix(share): render comment panel as full-width overlay on mobile viewports --- CHANGELOG.md | 1 + apps/web/app/share/[token]/page.tsx | 109 +----------------- .../components/share/folder-share-viewer.tsx | 4 +- 3 files changed, 5 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2f95bb..1a08cd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Dev compose honors the environment for LAN/self-host testing** — `docker-compose.dev.yml` now reads `NEXT_PUBLIC_API_URL`, the S3 storage credentials/bucket/region, `S3_PUBLIC_ENDPOINT`, and `MINIO_CORS_ALLOW_ORIGIN` from the environment (falling back to the previous defaults), so the dev stack can point at a LAN IP or external storage without editing the compose file. ### Fixed +- **Share viewer comment panel overlay on mobile viewports** ([#187](https://github.com/Techiebutler/freeframe/issues/187)) — below the `md` breakpoint (768px), the comment panel in both single-asset and folder share viewers now renders as a full-width overlay sheet over the media instead of a 360px flex sibling squeezing the player. - **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`. 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 && ( -
+