116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { useRouter, useParams } from 'next/navigation';
|
|
import Header from '../../components/Header';
|
|
import Sidebar from '../../components/Sidebar';
|
|
import AppIframeViewer from '../../components/AppIframeViewer';
|
|
import type { User, GeneratedApp } from '@/types';
|
|
|
|
export default function AppDetailPage() {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [app, setApp] = useState<GeneratedApp | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const id = params?.id as string;
|
|
|
|
useEffect(() => {
|
|
// Fetch current user
|
|
fetch('/api/auth/me')
|
|
.then(res => {
|
|
if (!res.ok) {
|
|
router.push('/login');
|
|
return null;
|
|
}
|
|
return res.json();
|
|
})
|
|
.then(data => {
|
|
if (data) setUser(data);
|
|
})
|
|
.catch(() => router.push('/login'));
|
|
|
|
// Fetch app
|
|
if (id) {
|
|
fetch(`/api/apps/${id}`)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('App not found');
|
|
return res.json();
|
|
})
|
|
.then(data => {
|
|
setApp(data);
|
|
})
|
|
.catch(() => router.push('/apps'))
|
|
.finally(() => setIsLoading(false));
|
|
}
|
|
}, [id, router]);
|
|
|
|
const handleLoadComplete = () => {
|
|
console.log('App iframe loaded');
|
|
};
|
|
|
|
if (isLoading || !app) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen bg-gray-50">
|
|
<Sidebar activePath="/apps" />
|
|
<div className="flex-1">
|
|
<Header user={user} />
|
|
<main className="p-6">
|
|
<div className="max-w-6xl mx-auto">
|
|
<div className="mb-6">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
{app.title}
|
|
</h1>
|
|
{app.description && (
|
|
<p className="text-gray-600">{app.description}</p>
|
|
)}
|
|
<div className="flex gap-2 mt-2">
|
|
{app.appType && (
|
|
<span className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded">
|
|
{app.appType}
|
|
</span>
|
|
)}
|
|
<span className={`text-xs px-2 py-1 rounded ${
|
|
app.status === 'completed' ? 'bg-green-100 text-green-800' :
|
|
app.status === 'generating' ? 'bg-yellow-100 text-yellow-800' :
|
|
'bg-red-100 text-red-800'
|
|
}`}>
|
|
{app.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{app.status === 'completed' && (
|
|
<AppIframeViewer
|
|
htmlContent={app.htmlContent}
|
|
title={app.title}
|
|
onLoadComplete={handleLoadComplete}
|
|
/>
|
|
)}
|
|
|
|
{app.status === 'generating' && (
|
|
<div className="bg-white border rounded-lg p-12 text-center">
|
|
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
|
<p className="text-gray-600">Generating app...</p>
|
|
</div>
|
|
)}
|
|
|
|
{app.status === 'failed' && (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
|
<p className="text-red-700">App generation failed</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|