mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
feat: optimize bundle size and improve image loading
- Unify video page components into single VideoPageContent component
- Reduces ~2300 lines of duplicated code between dashboard and watch pages
- Uses mode prop ('dashboard' | 'watch') to handle differences
- Move shadcn to devDependencies (CLI tool, not needed at runtime)
- Remove radix-ui meta-package (components already imported individually)
- Replace <img> tags with next/image for automatic optimization
- Add remotePatterns config for YouTube, Vimeo, Unsplash domains
- Update video-card.tsx, videos/new/page.tsx, component-example.tsx
- Add next/dynamic for KeyboardShortcutsModal (lazy load on demand)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { ArrowLeft, Loader2, Link as LinkIcon, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -177,10 +178,12 @@ export default function NewVideoPage() {
|
||||
<div className="space-y-2">
|
||||
<Label>Preview</Label>
|
||||
<div className="relative aspect-video rounded-lg overflow-hidden bg-muted">
|
||||
<img
|
||||
<Image
|
||||
src={thumbnailUrl}
|
||||
alt="Video thumbnail"
|
||||
className="object-cover w-full h-full"
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 600px"
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+4
-2127
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
import {
|
||||
Example,
|
||||
@@ -81,10 +82,12 @@ function CardExample() {
|
||||
<Example title="Card" className="items-center justify-center">
|
||||
<Card className="relative w-full max-w-sm overflow-hidden pt-0">
|
||||
<div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
|
||||
<img
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
alt="Photo by mymind on Unsplash"
|
||||
title="Photo by mymind on Unsplash"
|
||||
width={400}
|
||||
height={225}
|
||||
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
|
||||
/>
|
||||
<CardHeader>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {
|
||||
Video,
|
||||
@@ -25,9 +26,13 @@ import {
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { KeyboardShortcutsModal } from '@/components/keyboard-shortcuts-modal';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const KeyboardShortcutsModal = dynamic(
|
||||
() => import('@/components/keyboard-shortcuts-modal').then(mod => mod.KeyboardShortcutsModal),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface NavItem {
|
||||
href: string;
|
||||
label: string;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import {
|
||||
Play,
|
||||
@@ -178,10 +179,12 @@ export function VideoCard({ video, projectId }: VideoCardProps) {
|
||||
<Link href={`/projects/${projectId}/videos/${video.id}`}>
|
||||
{/* Thumbnail */}
|
||||
<div className="relative aspect-video bg-muted overflow-hidden">
|
||||
<img
|
||||
<Image
|
||||
src={video.thumbnailUrl}
|
||||
alt={video.title}
|
||||
className="object-cover w-full h-full transition-transform group-hover:scale-105"
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||||
<Play className="h-12 w-12 text-white" fill="white" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+9
-1
@@ -1,7 +1,15 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{ protocol: 'https', hostname: 'img.youtube.com' },
|
||||
{ protocol: 'https', hostname: 'i.ytimg.com' },
|
||||
{ protocol: 'https', hostname: 'i.vimeocdn.com' },
|
||||
{ protocol: 'https', hostname: 'vumbnail.com' },
|
||||
{ protocol: 'https', hostname: 'images.unsplash.com' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
+1
-2
@@ -35,10 +35,8 @@
|
||||
"nodemailer": "^8.0.1",
|
||||
"pg": "^8.18.0",
|
||||
"prisma": "^7.3.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"shadcn": "^3.8.3",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
@@ -54,6 +52,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"shadcn": "^3.8.3",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user