Skip to content

Commit 565fcaf

Browse files
committed
Redesign video grid, reel, and admin upload UX
Refactors the admin and client share pages to use a fixed layout with improved theme toggling, streamlined grid and reel navigation, and a more responsive comment panel. The admin video manager now supports drag-and-drop and multi-file uploads with a pending upload queue, replacing the previous single-form approach. Video and group sorting is improved to prioritize review videos, and UI elements such as buttons and tooltips are enhanced for clarity and accessibility. Updates also include subtle color adjustments for the light theme and minor UI consistency fixes across components.
1 parent e8de375 commit 565fcaf

14 files changed

Lines changed: 716 additions & 567 deletions

src/app/admin/projects/[id]/share/page.tsx

Lines changed: 119 additions & 162 deletions
Large diffs are not rendered by default.

src/app/globals.css

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
@layer base {
66
/* Light Mode Theme */
77
:root {
8-
/* Base colors - light theme */
9-
--background: 0 0% 98%;
8+
/* Base colors - light theme (soft grey instead of white) */
9+
--background: 220 14% 92%;
1010
--foreground: 0 0% 13%;
1111

12-
/* Card colors - elevated surfaces */
13-
--card: 0 0% 100%;
12+
/* Card colors - elevated surfaces (slightly lighter than background) */
13+
--card: 220 14% 96%;
1414
--card-foreground: 0 0% 13%;
1515

1616
/* Popover colors */
17-
--popover: 0 0% 100%;
17+
--popover: 220 14% 96%;
1818
--popover-foreground: 0 0% 13%;
1919

2020
/* Primary - Blue (#007AFF) */
@@ -23,15 +23,15 @@
2323
--primary-visible: 211 100% 95%;
2424

2525
/* Secondary - Gray */
26-
--secondary: 240 5% 96%;
26+
--secondary: 220 14% 89%;
2727
--secondary-foreground: 0 0% 13%;
2828

2929
/* Muted - subtle backgrounds */
30-
--muted: 240 5% 96%;
30+
--muted: 220 14% 89%;
3131
--muted-foreground: 0 0% 45%;
3232

3333
/* Accent - tint color */
34-
--accent: 240 5% 96%;
34+
--accent: 220 14% 89%;
3535
--accent-foreground: 211 100% 50%;
3636

3737
/* Success - Green (#34C759) */
@@ -55,8 +55,8 @@
5555
--info-visible: 200 100% 95%;
5656

5757
/* Border and input */
58-
--border: 240 6% 90%;
59-
--input: 240 6% 90%;
58+
--border: 220 14% 85%;
59+
--input: 220 14% 85%;
6060
--ring: 211 100% 50%;
6161

6262
/* Border radius values */

src/app/share/[token]/SharePageClient.tsx

Lines changed: 83 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import VideoPlayer from '@/components/VideoPlayer'
66
import CommentSection from '@/components/CommentSection'
77
import ThumbnailGrid from '@/components/ThumbnailGrid'
88
import ThumbnailReel from '@/components/ThumbnailReel'
9-
import ProjectInfo from '@/components/ProjectInfo'
109
import { OTPInput } from '@/components/OTPInput'
1110
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
1211
import { Input } from '@/components/ui/input'
1312
import { PasswordInput } from '@/components/ui/password-input'
1413
import { Button } from '@/components/ui/button'
15-
import { Lock, Check, Mail, KeyRound, MessageSquare } from 'lucide-react'
14+
import { Lock, Check, Mail, KeyRound } from 'lucide-react'
1615
import { loadShareToken, saveShareToken } from '@/lib/share-token-store'
16+
import ThemeToggle from '@/components/ThemeToggle'
1717

1818
interface SharePageClientProps {
1919
token: string
@@ -57,12 +57,6 @@ export default function SharePageClient({ token }: SharePageClientProps) {
5757
const [initialVideoIndex, setInitialVideoIndex] = useState<number>(0)
5858
const [shareToken, setShareToken] = useState<string | null>(null)
5959
const [hideComments, setHideComments] = useState(false)
60-
const [videoState, setVideoState] = useState<{
61-
selectedVideo: any
62-
isVideoApproved: boolean
63-
displayVideos: any[]
64-
displayLabel: string
65-
} | null>(null)
6660
const [viewState, setViewState] = useState<'grid' | 'player'>('grid')
6761
const [thumbnailsByName, setThumbnailsByName] = useState<Map<string, string>>(new Map())
6862
const [thumbnailsLoading, setThumbnailsLoading] = useState(true)
@@ -849,12 +843,16 @@ export default function SharePageClient({ token }: SharePageClientProps) {
849843
return !comment.videoId || activeVideoIds.has(comment.videoId)
850844
})
851845

852-
// Show thumbnail grid for multi-video projects when in grid view
846+
// Show thumbnail grid for multi-video projects when in grid view (scrollable)
853847
if (viewState === 'grid' && hasMultipleVideos) {
854848
return (
855-
<div className="flex-1 min-h-0 bg-background flex flex-col overflow-hidden">
849+
<div className="fixed inset-0 bg-background flex flex-col overflow-hidden">
850+
{/* Theme toggle for grid view */}
851+
<div className="absolute top-3 right-3 z-20">
852+
<ThemeToggle />
853+
</div>
856854
<div className="flex-1 overflow-y-auto">
857-
<div className="w-full px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
855+
<div className="w-full px-3 sm:px-6 lg:px-8 py-4 sm:py-6 lg:py-8">
858856
<ThumbnailGrid
859857
videosByName={project.videosByName}
860858
thumbnailsByName={thumbnailsByName}
@@ -870,9 +868,12 @@ export default function SharePageClient({ token }: SharePageClientProps) {
870868
)
871869
}
872870

871+
// Whether to show comment panel (not hidden by project settings, user toggle, or guest status)
872+
const showCommentPanel = !project.hideFeedback && !isGuest && !hideComments
873+
873874
return (
874-
<div className="flex-1 min-h-0 bg-background flex flex-col overflow-hidden">
875-
{/* Thumbnail Reel for multi-video projects */}
875+
<div className="fixed inset-0 bg-background flex flex-col overflow-hidden">
876+
{/* Thumbnail Reel for multi-video projects - always visible, collapsible */}
876877
{hasMultipleVideos && (
877878
<ThumbnailReel
878879
videosByName={project.videosByName}
@@ -881,123 +882,91 @@ export default function SharePageClient({ token }: SharePageClientProps) {
881882
onVideoSelect={handleVideoSelect}
882883
onBackToGrid={handleBackToGrid}
883884
showBackButton={true}
885+
showCommentToggle={!project.hideFeedback && !isGuest}
886+
isCommentPanelVisible={!hideComments}
887+
onToggleCommentPanel={() => setHideComments(!hideComments)}
884888
/>
885889
)}
886890

887-
{/* Main Content Area */}
888-
<div className="flex-1 flex flex-col min-w-0 overflow-y-auto">
889-
{/* Content Area */}
890-
<div className="w-full px-4 sm:px-6 lg:px-8 py-4 sm:py-8 flex-1 min-h-0 flex flex-col">
891-
{/* Content Area */}
892-
{readyVideos.length === 0 ? (
891+
{/* Theme toggle for single-video projects (floating in top-right) */}
892+
{!hasMultipleVideos && (
893+
<div className="absolute top-3 right-3 z-20">
894+
<ThemeToggle />
895+
</div>
896+
)}
897+
898+
{/* Main Content Area - fills remaining height */}
899+
<div className="flex-1 min-h-0 flex flex-col lg:flex-row p-2 sm:p-3 gap-2 sm:gap-3">
900+
{readyVideos.length === 0 ? (
901+
<div className="flex-1 flex items-center justify-center p-4">
893902
<Card className="bg-card border-border">
894903
<CardContent className="py-12 text-center">
895904
<p className="text-muted-foreground">
896905
{tokensLoading ? 'Loading video...' : 'No videos are ready for review yet. Please check back later.'}
897906
</p>
898907
</CardContent>
899908
</Card>
900-
) : (
901-
<div className={`flex-1 min-h-0 ${(project.hideFeedback || isGuest || hideComments) ? 'flex flex-col max-w-7xl mx-auto w-full' : 'flex flex-col lg:grid gap-4 sm:gap-6 lg:grid-cols-3'}`}>
902-
{/* Video Player - order-1 on both mobile and desktop */}
903-
<div className={`${(project.hideFeedback || isGuest || hideComments) ? 'flex-1 min-h-0 flex flex-col relative' : 'order-1 lg:col-span-2'}`}>
904-
{/* Show Comments Toggle Button - visible when comments are hidden */}
905-
{!project.hideFeedback && !isGuest && hideComments && (
906-
<Button
907-
onClick={() => setHideComments(false)}
908-
variant="outline"
909-
size="sm"
910-
className="absolute top-4 right-4 z-10 hidden lg:flex items-center gap-2 bg-background/95 backdrop-blur-sm shadow-lg hover:bg-background"
911-
title="Show feedback & discussion"
912-
>
913-
<MessageSquare className="w-4 h-4" />
914-
Show Feedback
915-
</Button>
916-
)}
917-
<VideoPlayer
918-
videos={readyVideos}
909+
</div>
910+
) : (
911+
<>
912+
{/* Video Player - fills available space */}
913+
<div className={`flex-1 min-h-0 min-w-0 flex flex-col rounded-xl overflow-hidden ${showCommentPanel ? 'lg:flex-[2] xl:flex-[2.5]' : ''}`}>
914+
<VideoPlayer
915+
videos={readyVideos}
916+
projectId={project.id}
917+
projectStatus={project.status}
918+
defaultQuality={defaultQuality}
919+
projectTitle={project.title}
920+
projectDescription={isGuest ? null : project.description}
921+
clientName={isGuest ? null : project.clientName}
922+
isPasswordProtected={isPasswordProtected || false}
923+
watermarkEnabled={project.watermarkEnabled}
924+
activeVideoName={activeVideoName}
925+
onApprove={isGuest ? undefined : fetchProjectData}
926+
initialSeekTime={initialSeekTime}
927+
initialVideoIndex={initialVideoIndex}
928+
isAdmin={false}
929+
isGuest={isGuest}
930+
allowAssetDownload={project.allowAssetDownload}
931+
clientCanApprove={project.clientCanApprove}
932+
shareToken={shareToken}
933+
comments={!project.hideFeedback && !isGuest ? filteredComments : []}
934+
timestampDisplayMode={project.timestampDisplay || 'TIMECODE'}
935+
onCommentFocus={(commentId) => setFocusCommentId(commentId)}
936+
usePreviewForApprovedPlayback={project.usePreviewForApprovedPlayback}
937+
fillContainer={true}
938+
/>
939+
</div>
940+
941+
{/* Comments Section - proportional side panel on desktop, collapsible on mobile */}
942+
{showCommentPanel && (
943+
<div className="shrink-0 lg:shrink lg:flex-1 lg:max-w-[30%] xl:max-w-[25%] lg:min-w-[280px] flex flex-col max-h-[35vh] lg:max-h-full lg:h-full overflow-hidden rounded-xl bg-card">
944+
<CommentSection
919945
projectId={project.id}
920-
projectStatus={project.status}
921-
defaultQuality={defaultQuality}
922-
projectTitle={project.title}
923-
projectDescription={isGuest ? null : project.description}
924-
clientName={isGuest ? null : project.clientName}
946+
comments={filteredComments}
947+
focusCommentId={focusCommentId}
948+
clientName={project.clientName}
949+
clientEmail={project.clientEmail}
950+
isApproved={project.status === 'APPROVED' || project.status === 'SHARE_ONLY'}
951+
restrictToLatestVersion={project.restrictCommentsToLatestVersion}
952+
videos={readyVideos}
953+
isAdminView={false}
954+
smtpConfigured={project.smtpConfigured}
925955
isPasswordProtected={isPasswordProtected || false}
926-
watermarkEnabled={project.watermarkEnabled}
927-
activeVideoName={activeVideoName}
928-
onApprove={isGuest ? undefined : fetchProjectData}
929-
initialSeekTime={initialSeekTime}
930-
initialVideoIndex={initialVideoIndex}
931-
isAdmin={false}
932-
isGuest={isGuest}
933-
allowAssetDownload={project.allowAssetDownload}
934-
clientCanApprove={project.clientCanApprove}
956+
recipients={project.recipients || []}
935957
shareToken={shareToken}
936-
comments={!project.hideFeedback && !isGuest ? filteredComments : []}
958+
showShortcutsButton={true}
937959
timestampDisplayMode={project.timestampDisplay || 'TIMECODE'}
938-
onCommentFocus={(commentId) => setFocusCommentId(commentId)}
939-
onVideoStateChange={setVideoState}
940-
usePreviewForApprovedPlayback={project.usePreviewForApprovedPlayback}
960+
mobileCollapsible={true}
961+
initialMobileCollapsed={true}
962+
authenticatedEmail={authenticatedEmail}
963+
onToggleVisibility={() => setHideComments(!hideComments)}
964+
showToggleButton={false}
941965
/>
942966
</div>
943-
944-
{/* Comments Section (input + collapsible messages) - order-2 */}
945-
{!project.hideFeedback && !isGuest && !hideComments && (
946-
<div className="order-2 lg:sticky lg:top-6 lg:self-start">
947-
<CommentSection
948-
projectId={project.id}
949-
comments={filteredComments}
950-
focusCommentId={focusCommentId}
951-
clientName={project.clientName}
952-
clientEmail={project.clientEmail}
953-
isApproved={project.status === 'APPROVED' || project.status === 'SHARE_ONLY'}
954-
restrictToLatestVersion={project.restrictCommentsToLatestVersion}
955-
videos={readyVideos}
956-
isAdminView={false}
957-
smtpConfigured={project.smtpConfigured}
958-
isPasswordProtected={isPasswordProtected || false}
959-
recipients={project.recipients || []}
960-
shareToken={shareToken}
961-
showShortcutsButton={true}
962-
timestampDisplayMode={project.timestampDisplay || 'TIMECODE'}
963-
mobileCollapsible={true}
964-
initialMobileCollapsed={true}
965-
authenticatedEmail={authenticatedEmail}
966-
onToggleVisibility={() => setHideComments(!hideComments)}
967-
showToggleButton={true}
968-
/>
969-
</div>
970-
)}
971-
972-
{/* Project Info - order-3 on mobile only, hidden on desktop */}
973-
{videoState && !isGuest && (
974-
<div className="order-3 lg:hidden">
975-
<ProjectInfo
976-
selectedVideo={videoState.selectedVideo}
977-
displayLabel={videoState.displayLabel}
978-
isVideoApproved={videoState.isVideoApproved}
979-
projectId={project.id}
980-
projectTitle={project.title}
981-
projectDescription={project.description}
982-
clientName={project.clientName}
983-
isPasswordProtected={isPasswordProtected || false}
984-
watermarkEnabled={project.watermarkEnabled}
985-
defaultQuality={defaultQuality}
986-
onApprove={fetchProjectData}
987-
isAdmin={false}
988-
clientCanApprove={project.clientCanApprove}
989-
isGuest={false}
990-
hideDownloadButton={false}
991-
allowAssetDownload={project.allowAssetDownload}
992-
shareToken={shareToken}
993-
activeVideoName={activeVideoName}
994-
usePreviewForApprovedPlayback={project.usePreviewForApprovedPlayback}
995-
/>
996-
</div>
997-
)}
998-
</div>
999-
)}
1000-
</div>
967+
)}
968+
</>
969+
)}
1001970
</div>
1002971
</div>
1003972
)

0 commit comments

Comments
 (0)