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 { useState, useEffect } from 'react';
|
||||||
import { useRouter, useParams } from 'next/navigation';
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
import { ArrowLeft, Loader2, Link as LinkIcon, AlertCircle, CheckCircle2 } from 'lucide-react';
|
import { ArrowLeft, Loader2, Link as LinkIcon, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -177,10 +178,12 @@ export default function NewVideoPage() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Preview</Label>
|
<Label>Preview</Label>
|
||||||
<div className="relative aspect-video rounded-lg overflow-hidden bg-muted">
|
<div className="relative aspect-video rounded-lg overflow-hidden bg-muted">
|
||||||
<img
|
<Image
|
||||||
src={thumbnailUrl}
|
src={thumbnailUrl}
|
||||||
alt="Video thumbnail"
|
alt="Video thumbnail"
|
||||||
className="object-cover w-full h-full"
|
fill
|
||||||
|
sizes="(max-width: 768px) 100vw, 600px"
|
||||||
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-2129
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Example,
|
Example,
|
||||||
@@ -81,10 +82,12 @@ function CardExample() {
|
|||||||
<Example title="Card" className="items-center justify-center">
|
<Example title="Card" className="items-center justify-center">
|
||||||
<Card className="relative w-full max-w-sm overflow-hidden pt-0">
|
<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" />
|
<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"
|
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"
|
alt="Photo by mymind on Unsplash"
|
||||||
title="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"
|
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
|
||||||
/>
|
/>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Video,
|
Video,
|
||||||
@@ -25,9 +26,13 @@ import {
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||||
import { ThemeToggle } from '@/components/theme-toggle';
|
import { ThemeToggle } from '@/components/theme-toggle';
|
||||||
import { KeyboardShortcutsModal } from '@/components/keyboard-shortcuts-modal';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const KeyboardShortcutsModal = dynamic(
|
||||||
|
() => import('@/components/keyboard-shortcuts-modal').then(mod => mod.KeyboardShortcutsModal),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
href: string;
|
href: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Play,
|
Play,
|
||||||
@@ -178,10 +179,12 @@ export function VideoCard({ video, projectId }: VideoCardProps) {
|
|||||||
<Link href={`/projects/${projectId}/videos/${video.id}`}>
|
<Link href={`/projects/${projectId}/videos/${video.id}`}>
|
||||||
{/* Thumbnail */}
|
{/* Thumbnail */}
|
||||||
<div className="relative aspect-video bg-muted overflow-hidden">
|
<div className="relative aspect-video bg-muted overflow-hidden">
|
||||||
<img
|
<Image
|
||||||
src={video.thumbnailUrl}
|
src={video.thumbnailUrl}
|
||||||
alt={video.title}
|
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">
|
<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" />
|
<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";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
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;
|
export default nextConfig;
|
||||||
|
|||||||
+1
-2
@@ -35,10 +35,8 @@
|
|||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.1",
|
||||||
"pg": "^8.18.0",
|
"pg": "^8.18.0",
|
||||||
"prisma": "^7.3.0",
|
"prisma": "^7.3.0",
|
||||||
"radix-ui": "^1.4.3",
|
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"shadcn": "^3.8.3",
|
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
@@ -54,6 +52,7 @@
|
|||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
|
"shadcn": "^3.8.3",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user