generated from fahricansecer/boilerplate-fe
main
Some checks are pending
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Waiting to run
Some checks are pending
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Waiting to run
This commit is contained in:
@@ -33,6 +33,7 @@ import { SceneCard } from '@/components/project/scene-card';
|
|||||||
import { RenderProgress } from '@/components/project/render-progress';
|
import { RenderProgress } from '@/components/project/render-progress';
|
||||||
import { VideoPlayer } from '@/components/project/video-player';
|
import { VideoPlayer } from '@/components/project/video-player';
|
||||||
import { projectsApi } from '@/lib/api/api-service';
|
import { projectsApi } from '@/lib/api/api-service';
|
||||||
|
import { CINEMATIC_REFERENCES } from '@/constants/cinematic-references';
|
||||||
|
|
||||||
// X (Twitter) ikonunu burada da tanımlıyoruz
|
// X (Twitter) ikonunu burada da tanımlıyoruz
|
||||||
const XIcon = ({ size = 16 }: { size?: number }) => (
|
const XIcon = ({ size = 16 }: { size?: number }) => (
|
||||||
@@ -255,8 +256,18 @@ export default function ProjectDetailPage() {
|
|||||||
try {
|
try {
|
||||||
await projectsApi.update(id, { videoStyle: newStyleId } as any);
|
await projectsApi.update(id, { videoStyle: newStyleId } as any);
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Üslup (Stil) değiştirme hatası:', err);
|
console.error('Failed to update style:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCinematicReferenceChange = async (newRef: string) => {
|
||||||
|
if (newRef === project.cinematicReference) return;
|
||||||
|
try {
|
||||||
|
await projectsApi.update(id, { cinematicReference: newRef || undefined } as any);
|
||||||
|
refetch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update cinematic reference:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -354,6 +365,24 @@ export default function ProjectDetailPage() {
|
|||||||
) : (
|
) : (
|
||||||
<span>{currentStyle ? `${currentStyle.emoji} ${currentStyle.label}` : project.videoStyle}</span>
|
<span>{currentStyle ? `${currentStyle.emoji} ${currentStyle.label}` : project.videoStyle}</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{project.videoStyle === 'CINEMATIC' && isEditable ? (
|
||||||
|
<select
|
||||||
|
value={project.cinematicReference || ''}
|
||||||
|
onChange={(e) => handleCinematicReferenceChange(e.target.value)}
|
||||||
|
className="bg-[var(--color-bg-base)] border border-[var(--color-border-faint)] rounded-md px-2 py-0.5 text-xs text-[var(--color-text-secondary)] focus:outline-none focus:border-violet-500/50 cursor-pointer w-44 truncate"
|
||||||
|
>
|
||||||
|
<option value="">🎬 Sinematik Yönetmen/Film...</option>
|
||||||
|
{CINEMATIC_REFERENCES.map(ref => (
|
||||||
|
<option key={ref.value} value={ref.value}>{ref.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
) : project.videoStyle === 'CINEMATIC' && project.cinematicReference ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
🎬 <span className="truncate max-w-[150px]">{project.cinematicReference}</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<span className="uppercase text-[10px] tracking-wider">{project.language}</span>
|
<span className="uppercase text-[10px] tracking-wider">{project.language}</span>
|
||||||
<span className="text-[10px]">
|
<span className="text-[10px]">
|
||||||
{new Date(project.createdAt).toLocaleDateString('tr-TR', {
|
{new Date(project.createdAt).toLocaleDateString('tr-TR', {
|
||||||
@@ -485,7 +514,11 @@ export default function ProjectDetailPage() {
|
|||||||
isEditable={isEditable}
|
isEditable={isEditable}
|
||||||
onUpdate={handleSceneUpdate}
|
onUpdate={handleSceneUpdate}
|
||||||
onRegenerate={handleSceneRegenerate}
|
onRegenerate={handleSceneRegenerate}
|
||||||
|
onGenerateImage={handleGenerateImage}
|
||||||
|
onUpscaleImage={handleUpscaleImage}
|
||||||
isRegenerating={regeneratingSceneId === scene.id}
|
isRegenerating={regeneratingSceneId === scene.id}
|
||||||
|
isGeneratingImage={generatingImageId === scene.id}
|
||||||
|
isUpscalingImage={upscalingImageId === scene.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useProjects } from "@/hooks/use-api";
|
import { useProjects, useDeleteProject } from "@/hooks/use-api";
|
||||||
|
|
||||||
const statusFilters = [
|
const statusFilters = [
|
||||||
{ id: "all", label: "Tümü" },
|
{ id: "all", label: "Tümü" },
|
||||||
@@ -84,6 +85,15 @@ export default function ProjectsPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
const { data, isLoading } = useProjects({ limit: 100 });
|
const { data, isLoading } = useProjects({ limit: 100 });
|
||||||
|
const deleteMutation = useDeleteProject();
|
||||||
|
|
||||||
|
const handleDelete = (e: React.MouseEvent, id: string) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (confirm("Bu projeyi silmek istediğinize emin misiniz?")) {
|
||||||
|
deleteMutation.mutate(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
// useProjects returns PaginatedResponse<Project> which has .data as Project[]
|
// useProjects returns PaginatedResponse<Project> which has .data as Project[]
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const raw = data as any;
|
const raw = data as any;
|
||||||
@@ -243,10 +253,20 @@ export default function ProjectsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`text-[10px] font-medium px-2.5 py-1 rounded-full border ${st.color} border-current/20 ${st.bgColor} shrink-0`}
|
className={`text-[10px] font-medium px-2.5 py-1 rounded-full border ${st.color} border-current/20 ${st.bgColor} shrink-0 mr-2`}
|
||||||
>
|
>
|
||||||
{st.label}
|
{st.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={(e) => handleDelete(e, project.id)}
|
||||||
|
className="p-2 rounded-lg text-[var(--color-text-ghost)] hover:text-red-400 hover:bg-red-500/10 transition-colors shrink-0 z-10 mr-1"
|
||||||
|
title="Projeyi Sil"
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
size={14}
|
size={14}
|
||||||
className="text-[var(--color-text-ghost)] group-hover:text-violet-400 transition-colors shrink-0"
|
className="text-[var(--color-text-ghost)] group-hover:text-violet-400 transition-colors shrink-0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Pencil, Check, X, RefreshCw, Clock, ArrowRight, Wand2, Image as ImageIcon, Mic, Maximize2 } from 'lucide-react';
|
import { Pencil, Check, X, RefreshCw, Clock, ArrowRight, Wand2, Image as ImageIcon, Mic, Maximize2, Sparkles } from 'lucide-react';
|
||||||
|
|
||||||
interface SceneCardProps {
|
interface SceneCardProps {
|
||||||
scene: {
|
scene: {
|
||||||
@@ -92,7 +92,7 @@ export function SceneCard({
|
|||||||
|
|
||||||
{/* Aksiyon butonları */}
|
{/* Aksiyon butonları */}
|
||||||
{isEditable && !isEditing && (
|
{isEditable && !isEditing && (
|
||||||
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
<div className="flex items-center gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditing(true)}
|
onClick={() => setIsEditing(true)}
|
||||||
className="w-7 h-7 rounded-lg flex items-center justify-center text-[var(--color-text-muted)] hover:text-violet-400 hover:bg-violet-500/10 transition-colors"
|
className="w-7 h-7 rounded-lg flex items-center justify-center text-[var(--color-text-muted)] hover:text-violet-400 hover:bg-violet-500/10 transition-colors"
|
||||||
@@ -201,7 +201,7 @@ export function SceneCard({
|
|||||||
|
|
||||||
{/* Görsel / Upscale Alanı */}
|
{/* Görsel / Upscale Alanı */}
|
||||||
<div className="flex flex-col gap-2 pt-2">
|
<div className="flex flex-col gap-2 pt-2">
|
||||||
{thumbnailAsset?.url ? (
|
{thumbnailAsset?.url && !isGeneratingImage ? (
|
||||||
<div className="relative group/thumb rounded-lg overflow-hidden border border-[var(--color-border-faint)] aspect-video max-w-sm">
|
<div className="relative group/thumb rounded-lg overflow-hidden border border-[var(--color-border-faint)] aspect-video max-w-sm">
|
||||||
<img
|
<img
|
||||||
src={thumbnailAsset.url}
|
src={thumbnailAsset.url}
|
||||||
@@ -214,9 +214,24 @@ export function SceneCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-lg border border-dashed border-[var(--color-border-faint)] bg-[var(--color-bg-deep)] aspect-video max-w-sm flex flex-col items-center justify-center p-4">
|
<div className="rounded-lg border border-dashed border-[var(--color-border-faint)] bg-[var(--color-bg-deep)] aspect-video max-w-sm flex flex-col items-center justify-center p-4 relative overflow-hidden">
|
||||||
<ImageIcon size={24} className="text-[var(--color-text-ghost)] mb-2" />
|
{isGeneratingImage ? (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] text-center">Görsel Henüz Üretilmedi</p>
|
<div className="flex flex-col items-center justify-center animate-in fade-in zoom-in duration-300">
|
||||||
|
<div className="relative w-12 h-12 mb-3">
|
||||||
|
<div className="absolute inset-0 rounded-full border-2 border-emerald-500/20"></div>
|
||||||
|
<div className="absolute inset-0 rounded-full border-2 border-emerald-500 border-t-transparent animate-spin"></div>
|
||||||
|
<Sparkles size={16} className="absolute inset-0 m-auto text-emerald-400 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs font-medium text-emerald-400 text-center animate-pulse">
|
||||||
|
AI Görsel Üretiyor...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ImageIcon size={24} className="text-[var(--color-text-ghost)] mb-2" />
|
||||||
|
<p className="text-xs text-[var(--color-text-muted)] text-center">Görsel Henüz Üretilmedi</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
30
src/constants/cinematic-references.ts
Normal file
30
src/constants/cinematic-references.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export const CINEMATIC_REFERENCES = [
|
||||||
|
// Yönetmen Stilleri
|
||||||
|
{ value: 'Denis Villeneuve', label: 'Denis Villeneuve (Blade Runner 2049, Dune)' },
|
||||||
|
{ value: 'Christopher Nolan', label: 'Christopher Nolan (Interstellar, Inception)' },
|
||||||
|
{ value: 'Wes Anderson', label: 'Wes Anderson (Simetri, Pastel Renkler)' },
|
||||||
|
{ value: 'Stanley Kubrick', label: 'Stanley Kubrick (Tek Nokta Perspektifi, Derinlik)' },
|
||||||
|
{ value: 'Quentin Tarantino', label: 'Quentin Tarantino (Dinamik Açılar, Canlı Renkler)' },
|
||||||
|
{ value: 'Zack Snyder', label: 'Zack Snyder (Karanlık, Ağır Çekim, Yüksek Kontrast)' },
|
||||||
|
{ value: 'Hayao Miyazaki', label: 'Hayao Miyazaki / Studio Ghibli (Anime, Sıcak, Büyülü)' },
|
||||||
|
{ value: 'Tim Burton', label: 'Tim Burton (Gotik, Ekspresyonist)' },
|
||||||
|
{ value: 'Andrei Tarkovsky', label: 'Andrei Tarkovsky (Şiirsel, Yavaş, Atmosferik)' },
|
||||||
|
{ value: 'David Fincher', label: 'David Fincher (Karanlık, Yeşil/Sarı Tonlar)' },
|
||||||
|
{ value: 'George Miller', label: 'George Miller (Mad Max, Kaotik, Çöl Tonları)' },
|
||||||
|
{ value: 'Ridley Scott', label: 'Ridley Scott (Epik, Yoğun Işık, Dumanlı)' },
|
||||||
|
// Film ve Oyun Stilleri
|
||||||
|
{ value: 'The Matrix', label: 'The Matrix (Yeşil Filtre, Cyberpunk)' },
|
||||||
|
{ value: 'Cyberpunk 2077', label: 'Cyberpunk 2077 (Neon Işıklar, Fütüristik)' },
|
||||||
|
{ value: 'Stranger Things', label: 'Stranger Things (80ler Retro, Synthwave)' },
|
||||||
|
{ value: 'Arcane', label: 'Arcane (Boyanmış Animasyon, Steampunk)' },
|
||||||
|
{ value: 'Spider-Verse', label: 'Spider-Verse (Çizgi Roman Stili, Dinamik Kareler)' },
|
||||||
|
{ value: 'Lord of the Rings', label: 'Lord of the Rings (Epik Fantastik, Geniş Açılar)' },
|
||||||
|
{ value: 'Sopranos', label: 'Cinéma Vérité (Gerçekçi Belgesel Stili)' },
|
||||||
|
{ value: 'Sin City', label: 'Sin City (Siyah Beyaz, Kırmızı Vurgu)' },
|
||||||
|
// Dönem ve Sanat Stilleri
|
||||||
|
{ value: 'Noir', label: 'Film Noir (1940\'lar Suç, Siyah Beyaz)' },
|
||||||
|
{ value: 'Vintage 1970s', label: '70ler Vintage (Grenli, Sıcak Tonlar)' },
|
||||||
|
{ value: 'Vaporwave', label: 'Vaporwave (Mor, Pembe, Retro-PC)' },
|
||||||
|
{ value: 'Anime 90s', label: '90lar Anime (VHS Efektli, Nostaljik)' },
|
||||||
|
{ value: 'Pixar 3D', label: 'Pixar 3D (Tatlı, Yuvarlak Hatlar, Yumuşak Işık)' },
|
||||||
|
];
|
||||||
@@ -16,6 +16,7 @@ export interface Project {
|
|||||||
language: string;
|
language: string;
|
||||||
aspectRatio: string;
|
aspectRatio: string;
|
||||||
videoStyle: string;
|
videoStyle: string;
|
||||||
|
cinematicReference?: string;
|
||||||
targetDuration: number;
|
targetDuration: number;
|
||||||
creditsUsed: number;
|
creditsUsed: number;
|
||||||
thumbnailUrl?: string;
|
thumbnailUrl?: string;
|
||||||
@@ -135,17 +136,28 @@ export interface PaginatedResponse<T> {
|
|||||||
export interface CreateProjectPayload {
|
export interface CreateProjectPayload {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
topic?: string;
|
prompt: string;
|
||||||
prompt?: string;
|
|
||||||
language?: string;
|
language?: string;
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
style?: string;
|
|
||||||
videoStyle?: string;
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
targetDuration?: number;
|
targetDuration?: number;
|
||||||
seoKeywords?: string[];
|
seoKeywords?: string[];
|
||||||
referenceUrl?: string;
|
referenceUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateProjectPayload {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
prompt?: string;
|
||||||
|
language?: string;
|
||||||
|
aspectRatio?: string;
|
||||||
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
|
targetDuration?: number;
|
||||||
|
seoKeywords?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreditBalance {
|
export interface CreditBalance {
|
||||||
balance: number;
|
balance: number;
|
||||||
remaining: number;
|
remaining: number;
|
||||||
@@ -232,6 +244,7 @@ export interface CreateFromTweetPayload {
|
|||||||
language?: string;
|
language?: string;
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
videoStyle?: string;
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
targetDuration?: number;
|
targetDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user