Ver Fonte

feat: download button, owner name fix, mobile sidebar, portrait/landscape layout

- Add download button to video cards (project detail) and review page header
- Fix owner name not showing on flat (ungrouped) project card list
- Mobile sidebar: icon-only on small screens, full nav on md+
- Portrait mode: video top + comments below; landscape: side-by-side panel
- Portrait video area fixed at min-height 60vh; comments at min-height 40vh
Claude Dev há 1 mês atrás
pai
commit
58757b2f7e

+ 7 - 6
src/app/(dashboard)/layout.tsx

@@ -48,14 +48,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
     <div className="min-h-screen flex" style={{ background: 'var(--bg)' }}>
 
       {/* ── Sidebar ───────────────────────────────────────────── */}
-      <aside className="w-56 flex flex-col shrink-0"
+      <aside className="w-12 md:w-56 flex flex-col shrink-0"
              style={{
                background: 'rgba(10,11,20,0.95)',
                borderRight: '1px solid rgba(255,255,255,0.06)',
              }}>
 
         {/* Logo */}
-        <div className="px-4 py-5 flex items-center gap-2.5"
+        <div className="py-5 flex justify-center md:justify-start md:px-4"
              style={{ borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
           <Link href="/projects" className="flex items-center gap-2.5 group">
             <div className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0"
@@ -65,7 +65,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
                 <path strokeLinecap="round" strokeLinejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
               </svg>
             </div>
-            <span className="font-semibold tracking-tight text-sm" style={{ color: 'var(--text)' }}>
+            <span className="font-semibold tracking-tight text-sm hidden md:block" style={{ color: 'var(--text)' }}>
               VidReview
             </span>
           </Link>
@@ -118,11 +118,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
         </nav>
 
         {/* User */}
-        <div className="p-3" style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
+        <div className="py-3 flex justify-center md:justify-end px-3"
+             style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
           <div className="flex items-center gap-2.5 p-2 rounded-lg transition-colors cursor-default"
                style={{ border: '1px solid rgba(255,255,255,0.06)' }}>
             <Avatar name={user.name} size="md" />
-            <div className="flex-1 min-w-0">
+            <div className="flex-1 min-w-0 hidden md:block">
               <p className="text-sm font-medium truncate" style={{ color: 'var(--text)' }}>{user.name}</p>
               <p className="text-xs capitalize truncate" style={{ color: 'var(--text-muted)' }}>
                 {user.globalRole.toLowerCase()}
@@ -185,7 +186,7 @@ function NavLink({
       } : undefined}
     >
       <span className="shrink-0">{icon}</span>
-      {children}
+      <span className="hidden md:inline">{children}</span>
     </Link>
   );
 }

+ 14 - 0
src/app/(dashboard)/projects/[projectId]/page.tsx

@@ -681,6 +681,20 @@ export default function ProjectDetailPage() {
                         <span className="w-1 h-1 rounded-full" style={{ background: 'rgba(255,255,255,0.12)' }} />
                         <span>{new Date(asset.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</span>
                         <div className="flex-1" />
+
+                        {/* Download */}
+                        <a
+                          href={`/uploads/${asset.filePath}`}
+                          download={asset.filename}
+                          onClick={e => e.stopPropagation()}
+                          className="p-1 rounded transition-colors hover:bg-blue-500/20 flex-shrink-0"
+                          title="Download original"
+                        >
+                          <svg className="w-3.5 h-3.5" style={{ color: '#60A5FA' }} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
+                            <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
+                          </svg>
+                        </a>
+
                         {canManage && (
                           <button
                             onClick={(e) => { e.stopPropagation(); handleDeleteAsset(asset.id, asset.title); }}

+ 1 - 0
src/app/(dashboard)/projects/page.tsx

@@ -274,6 +274,7 @@ export default function ProjectsPage() {
                   project={project}
                   index={i}
                   onInvite={() => setInviteModal({ projectId: project.id, name: project.name })}
+                  showOwner={project.ownerId !== user?.id}
                 />
               ))
             )}

+ 57 - 5
src/app/review/[assetId]/page.tsx

@@ -63,6 +63,17 @@ export default function ReviewPage() {
   // The comment we're annotating (null = annotating the main video, not a specific comment)
   const [annotatingComment, setAnnotatingComment] = useState<Comment | null>(null);
 
+  // Portrait / landscape detection
+  const [isPortrait, setIsPortrait] = useState(false);
+
+  useEffect(() => {
+    const mq = window.matchMedia('(orientation: portrait)');
+    setIsPortrait(mq.matches);
+    const handler = (e: MediaQueryListEvent) => setIsPortrait(e.matches);
+    mq.addEventListener('change', handler);
+    return () => mq.removeEventListener('change', handler);
+  }, []);
+
   const isDraggingRef = useRef(false);
   const panelRef = useRef<HTMLDivElement>(null);
   const resizeStartRef = useRef<{ x: number; w: number } | null>(null);
@@ -366,6 +377,22 @@ export default function ReviewPage() {
 
         <div className="w-px h-5 shrink-0" style={{ background: 'rgba(255,255,255,0.08)' }} />
 
+        {/* Download */}
+        <a
+          href={`${API_BASE}/uploads/${asset.filePath}`}
+          download={asset.filename}
+          className="flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-md transition-all shrink-0"
+          style={{ color: '#60A5FA', background: 'rgba(96,165,250,0.08)' }}
+          title="Download original video"
+        >
+          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
+            <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
+          </svg>
+          <span className="hidden sm:inline">Download</span>
+        </a>
+
+        <div className="w-px h-5 shrink-0" style={{ background: 'rgba(255,255,255,0.08)' }} />
+
         {/* Status selector */}
         <div className="relative shrink-0">
           <button
@@ -409,10 +436,21 @@ export default function ReviewPage() {
       </header>
 
       {/* ── Body ───────────────────────────────────────────── */}
-      <div className="flex flex-1 overflow-hidden">
+      {/* Landscape: side-by-side | Portrait: stacked (video top, comments bottom) */}
+      <div
+        className="flex flex-1 overflow-hidden"
+        style={isPortrait
+          ? { flexDirection: 'column', overflowY: 'auto' }
+          : { flexDirection: 'row' }}
+      >
 
         {/* Video area */}
-        <div className="flex-1 overflow-y-auto p-4 flex flex-col gap-3 min-w-0">
+        <div
+          className="overflow-y-auto p-3 sm:p-4 flex flex-col gap-3 min-w-0"
+          style={isPortrait
+            ? { flex: 'none', width: '100%', minHeight: '60vh' }
+            : { flex: 1, overflowY: 'auto' }}
+        >
 
           <VideoPlayer
             src={videoUrl}
@@ -509,14 +547,28 @@ export default function ReviewPage() {
           </div>
         </div>
 
-        {/* Resize handle */}
-        <div className="resize-handle" onMouseDown={handleResizeStart} style={{ width: '4px' }} />
+        {/* Resize handle — only shown in landscape */}
+        {!isPortrait && (
+          <div className="resize-handle" onMouseDown={handleResizeStart} style={{ width: '4px' }} />
+        )}
 
         {/* ── Comment panel ─────────────────────────────────── */}
         <div
           ref={panelRef}
           className="flex flex-col overflow-hidden shrink-0"
-          style={{ width: panelWidth, background: 'rgba(10,11,20,0.98)', borderLeft: '1px solid rgba(255,255,255,0.06)' }}
+          style={isPortrait
+            ? {
+                flex: 1,
+                width: '100%',
+                minHeight: '40vh',
+                background: 'rgba(10,11,20,0.98)',
+                borderTop: '1px solid rgba(255,255,255,0.06)',
+              }
+            : {
+                width: panelWidth,
+                background: 'rgba(10,11,20,0.98)',
+                borderLeft: '1px solid rgba(255,255,255,0.06)',
+              }}
         >
           {/* Panel header */}
           <div className="px-4 py-3 flex items-center justify-between shrink-0"