diff --git a/docs/wiki/Admin-Settings.md b/docs/wiki/Admin-Settings.md index 240703b2..ecf6d5e4 100644 --- a/docs/wiki/Admin-Settings.md +++ b/docs/wiki/Admin-Settings.md @@ -179,6 +179,7 @@ Subscribe to project deadlines from any calendar app (Google Calendar, Apple Cal - Guest mode: view-only access without credentials. - Guest latest only: restrict guests to the latest version of each video (default: true). - Show photo albums to guests: guests can view photo albums; downloads stay disabled for guests (default: false). +- Photo-only delivery: the share link becomes a photo gallery and never exposes the project's videos. Recipients cannot see, stream, comment on, approve or download them, and video links handed out before the switch was turned on stop working. Admins keep full access (default: false). **Workflow** - Revision limit (enable/disable with max revisions count). diff --git a/docs/wiki/Client-Guide.md b/docs/wiki/Client-Guide.md index c621de8e..302b4c68 100644 --- a/docs/wiki/Client-Guide.md +++ b/docs/wiki/Client-Guide.md @@ -182,6 +182,15 @@ If the project contains photos, albums appear below the videos (for guests only - Select multiple photos (checkbox on each thumbnail) to download just those. - If the admin has enabled photo downloads, you can download your selection, the whole album, or all project photos as a ZIP. Photo downloads are never available in guest mode. +### Photo galleries + +When a share link delivers photos only (the project has no videos, or the admin turned on photo-only delivery), it opens straight into a gallery instead of the overview: + +- A full-screen cover with the project title; **View Gallery** scrolls down to the photos. +- The first album is already open. With several albums, they appear as tabs under the title; click one to switch. The address bar keeps the open album, so a copied link opens the same one. +- Photos sit in a masonry grid that keeps their original proportions. Click one to open the lightbox; use the arrow keys, or swipe on touch screens. +- With photo downloads enabled, the **Download** button in the top bar saves the open album or every album as a ZIP. With a mouse, hovering a photo shows a single-photo download and a circle that adds it to a selection you can download from the bar at the bottom. On touch screens, download single photos from the lightbox. + ## Downloading Download options appear after a video has been approved (not available in guest mode): diff --git a/docs/wiki/Features.md b/docs/wiki/Features.md index 647378f3..b4237c8d 100644 --- a/docs/wiki/Features.md +++ b/docs/wiki/Features.md @@ -15,6 +15,7 @@ - Video version comparison: side-by-side and slider overlay modes with synced playback. - Photo galleries: per-project albums with automatic webp thumbnails, admin-selectable covers, and a full-page client album view with lightbox and multi-select. Dropping folders onto the Photos section creates an album per folder. - Photo zip downloads on the share page (selected photos, one album, or the whole project), gated by a per-project toggle. +- Photo delivery gallery: share links with photos and no videos open on a full-screen cover with the albums as tabs and a masonry grid. A per-project photo-only switch keeps a project's videos off the share link entirely. - Recipient portal at `/portal`: recipients sign in with a magic link and see all active projects they are assigned to in one place. - Loop playback toggle in the video player. - Grid/list view toggle on the share page, applying to both videos and photo albums. diff --git a/docs/wiki/Usage-Guide.md b/docs/wiki/Usage-Guide.md index 8c8dd484..2ff8df58 100644 --- a/docs/wiki/Usage-Guide.md +++ b/docs/wiki/Usage-Guide.md @@ -124,6 +124,8 @@ Projects can hold photo albums alongside videos — useful for stills, BTS shots **Client side:** albums appear on the share page below the videos, with a full-page album view, lightbox, and multi-select. To let clients download photos (selection, album, or all project photos as a ZIP), enable **Allow photo downloads** in the project settings. +**Photo deliveries:** a share link that carries photos but no videos opens as a gallery: a full-screen cover, the albums as tabs, and a masonry grid with the first album already open. To deliver only photos from a project that also holds videos, enable **Photo-only delivery** in the project settings (Client Share Page). The link then exposes no video at all (page, streams, downloads, comments or approval), while you keep working on the videos in the admin area. Notifications sent for a photo-only project always announce the whole gallery. + ## Recipient portal Recipients added to projects can use the portal at `/portal` to see all their active projects in one place: diff --git a/prisma/migrations/20260930120000_add_photo_only_share/migration.sql b/prisma/migrations/20260930120000_add_photo_only_share/migration.sql new file mode 100644 index 00000000..218d5572 --- /dev/null +++ b/prisma/migrations/20260930120000_add_photo_only_share/migration.sql @@ -0,0 +1 @@ +ALTER TABLE "Project" ADD COLUMN "photoOnlyShare" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 52f30109..535f4d6b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -92,6 +92,7 @@ model Project { allowClientAssetUpload Boolean @default(false) // Allow clients to upload file attachments on comments clientCanApprove Boolean @default(true) // Allow clients to approve videos (false = admin only) allowReverseShare Boolean @default(false) // Allow clients to upload files directly to the project (reverse share) + photoOnlyShare Boolean @default(false) // Photo-only delivery: the share link serves photo albums only; videos stay admin-side // Approved playback settings usePreviewForApprovedPlayback Boolean @default(false) // Use browser-compatible preview instead of original after approval diff --git a/src/app/admin/projects/[id]/settings/page.tsx b/src/app/admin/projects/[id]/settings/page.tsx index caefd0a8..d3d9005c 100644 --- a/src/app/admin/projects/[id]/settings/page.tsx +++ b/src/app/admin/projects/[id]/settings/page.tsx @@ -54,6 +54,7 @@ interface Project { allowPhotoDownload: boolean allowClientAssetUpload: boolean allowReverseShare: boolean + photoOnlyShare: boolean clientCanApprove: boolean usePreviewForApprovedPlayback: boolean showClientTutorial: boolean @@ -107,6 +108,7 @@ export default function ProjectSettingsPage() { const [allowPhotoDownload, setAllowPhotoDownload] = useState(true) const [allowClientAssetUpload, setAllowClientAssetUpload] = useState(false) const [allowReverseShare, setAllowReverseShare] = useState(false) + const [photoOnlyShare, setPhotoOnlyShare] = useState(false) const [clientCanApprove, setClientCanApprove] = useState(true) const [usePreviewForApprovedPlayback, setUsePreviewForApprovedPlayback] = useState(false) const [showClientTutorial, setShowClientTutorial] = useState(true) @@ -193,6 +195,7 @@ export default function ProjectSettingsPage() { setAllowPhotoDownload(data.allowPhotoDownload ?? true) setAllowClientAssetUpload(data.allowClientAssetUpload ?? false) setAllowReverseShare(data.allowReverseShare ?? false) + setPhotoOnlyShare(data.photoOnlyShare ?? false) setClientCanApprove(data.clientCanApprove ?? true) setUsePreviewForApprovedPlayback(data.usePreviewForApprovedPlayback ?? false) setShowClientTutorial(data.showClientTutorial ?? true) @@ -307,6 +310,7 @@ export default function ProjectSettingsPage() { allowPhotoDownload, allowClientAssetUpload, allowReverseShare, + photoOnlyShare, clientCanApprove, usePreviewForApprovedPlayback, showClientTutorial, @@ -727,6 +731,23 @@ export default function ProjectSettingsPage() { // Client Share Page content const clientShareContent = ( <> + {/* ── Delivery ─────────────────────────────────────────────── */} +
+ {t('photoOnlyShareDescription')} +
+{tc('loading')}
++ {description} +
+ )} + +{title}
+ {photoTotal > 0 && ( ++ {t('photoCount', { count: photoTotal })} +
+ )} ++ {/* Without album access (guests on a link that hides photos from them) nothing will show up later */} + {projectId ? t('galleryEmpty') : t('noPhotosFound')} +
+ ) : photos.length === 0 ? ( +{t('noPhotosYet')}
+ ) : ( + <> +- {photo.fileName} - {index + 1} / {photos.length} -
++ {index + 1} / {count} +
+ ) : ( ++ {photo.fileName} + {index + 1} / {count} +
+ )}