main
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Failing after 1m16s

This commit is contained in:
Harun CAN
2026-02-14 13:49:32 +03:00
parent 05435cbaf8
commit eca4b8c652
3 changed files with 129 additions and 28 deletions

View File

@@ -11,7 +11,7 @@ const nextConfig: NextConfig = {
return [ return [
{ {
source: "/api/backend/:path*", source: "/api/backend/:path*",
destination: "http://localhost:3001/api/:path*", destination: "http://localhost:3000/api/:path*",
}, },
]; ];
}, },

View File

@@ -1,18 +1,14 @@
"use client"; "use client";
import { Box, Table, Badge, HStack, IconButton } from "@chakra-ui/react"; import { Box, Table, Badge, HStack, IconButton } from "@chakra-ui/react";
import { LuEye, LuPencil, LuTrash2 } from "react-icons/lu"; import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { LuEye, LuPencil, LuTrash2, LuRefreshCw } from "react-icons/lu";
import { toaster } from "@/components/ui/feedback/toaster"; import { toaster } from "@/components/ui/feedback/toaster";
import { useState } from "react";
import { ContentPreviewDialog } from "./ContentPreviewDialog"; import { ContentPreviewDialog } from "./ContentPreviewDialog";
const MOCK_CONTENT = [
{ id: 1, title: "The Future of AI in Marketing", platform: "LinkedIn", status: "published", date: "2024-03-10" },
{ id: 2, title: "5 Tips for Better Sleep", platform: "Twitter", status: "draft", date: "2024-03-12" },
{ id: 3, title: "Product Launch Announcement", platform: "Instagram", status: "scheduled", date: "2024-03-15" },
{ id: 4, title: "Weekly Tech Roundup", platform: "LinkedIn", status: "review", date: "2024-03-18" },
{ id: 5, title: "Customer Success Story", platform: "Blog", status: "draft", date: "2024-03-20" },
];
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
@@ -25,8 +21,36 @@ const getStatusColor = (status: string) => {
}; };
export function ContentTable() { export function ContentTable() {
const { data: session } = useSession();
const [selectedItem, setSelectedItem] = useState<any>(null); const [selectedItem, setSelectedItem] = useState<any>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [contentList, setContentList] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchContent = async () => {
if (!session?.accessToken) return;
setIsLoading(true);
try {
const res = await fetch('/api/backend/content', {
headers: {
'Authorization': `Bearer ${session.accessToken}`
}
});
if (res.ok) {
const data = await res.json();
setContentList(Array.isArray(data) ? data : []);
}
} catch (error) {
console.error("Failed to fetch content:", error);
} finally {
setIsLoading(false);
}
};
fetchContent();
}, [session]);
const handleAction = (action: string, item: any) => { const handleAction = (action: string, item: any) => {
if (action === 'View') { if (action === 'View') {
@@ -56,16 +80,31 @@ export function ContentTable() {
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{MOCK_CONTENT.map((item) => ( {isLoading ? (
<Table.Row>
<Table.Cell colSpan={5} textAlign="center" py={10}>
<HStack justify="center" gap={2}>
<LuRefreshCw className="animate-spin" />
<Box>Loading content...</Box>
</HStack>
</Table.Cell>
</Table.Row>
) : contentList.length === 0 ? (
<Table.Row>
<Table.Cell colSpan={5} textAlign="center" py={10}>
No content found. Start by generating some!
</Table.Cell>
</Table.Row>
) : contentList.map((item) => (
<Table.Row key={item.id}> <Table.Row key={item.id}>
<Table.Cell fontWeight="medium">{item.title}</Table.Cell> <Table.Cell fontWeight="medium">{item.title}</Table.Cell>
<Table.Cell>{item.platform}</Table.Cell> <Table.Cell>{item.type || item.platform}</Table.Cell>
<Table.Cell> <Table.Cell>
<Badge colorPalette={getStatusColor(item.status)} variant="solid"> <Badge colorPalette={getStatusColor(item.status)} variant="solid">
{item.status} {item.status}
</Badge> </Badge>
</Table.Cell> </Table.Cell>
<Table.Cell>{item.date}</Table.Cell> <Table.Cell>{new Date(item.createdAt || item.date).toLocaleDateString()}</Table.Cell>
<Table.Cell textAlign="right"> <Table.Cell textAlign="right">
<HStack justify="flex-end" gap={2}> <HStack justify="flex-end" gap={2}>
<IconButton <IconButton
@@ -101,6 +140,7 @@ export function ContentTable() {
</Table.Root> </Table.Root>
</Box> </Box>
<ContentPreviewDialog <ContentPreviewDialog
item={selectedItem} item={selectedItem}
open={isPreviewOpen} open={isPreviewOpen}

View File

@@ -1,12 +1,15 @@
"use client"; "use client";
import { Box, Heading, Steps, VStack, Input, Button, Text, HStack, Textarea, Card, SimpleGrid, Badge } from "@chakra-ui/react"; import { Box, Heading, Steps, VStack, Input, Button, Text, HStack, Textarea, Card, SimpleGrid, Badge, Progress, Spinner } from "@chakra-ui/react";
import { LuSparkles, LuArrowRight, LuCheck, LuHash } from "react-icons/lu"; import { LuSparkles, LuArrowRight, LuCheck, LuHash, LuRefreshCw } from "react-icons/lu";
import { toaster } from "@/components/ui/feedback/toaster"; import { toaster } from "@/components/ui/feedback/toaster";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { GeneratedContentResult } from "./GeneratedContentResult"; import { GeneratedContentResult } from "./GeneratedContentResult";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useSearchParams } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
// Platform Data (can also be fetched from backend if dynamic) // Platform Data (can also be fetched from backend if dynamic)
const PLATFORMS = [ const PLATFORMS = [
@@ -39,6 +42,10 @@ export function GenerateWizard() {
const [selectedNiche, setSelectedNiche] = useState(""); const [selectedNiche, setSelectedNiche] = useState("");
const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([]); const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([]);
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [generationStage, setGenerationStage] = useState("");
const [generationProgress, setGenerationProgress] = useState(0);
const router = useRouter();
const [niches, setNiches] = useState<Niche[]>([]); const [niches, setNiches] = useState<Niche[]>([]);
const [isLoadingNiches, setIsLoadingNiches] = useState(false); const [isLoadingNiches, setIsLoadingNiches] = useState(false);
@@ -133,19 +140,36 @@ export function GenerateWizard() {
const handleGenerate = async () => { const handleGenerate = async () => {
setIsGenerating(true); setIsGenerating(true);
setGeneratedBundle(null);
setGenerationProgress(10);
setGenerationStage("Researching topic and niche...");
try { try {
const payload = { const payload = {
topic, topic,
description: trendDescription || undefined, description: trendDescription || undefined,
keywords: trendKeywords.length > 0 ? trendKeywords : undefined, keywords: trendKeywords.length > 0 ? trendKeywords : undefined,
niche: selectedNiche, // The service expects 'niche' as string ID niche: selectedNiche,
platforms: selectedPlatforms, platforms: selectedPlatforms,
includeResearch: true, includeResearch: true,
includeHashtags: true, includeHashtags: true,
brandVoice: "friendly-expert", // Default for now, can be added to UI brandVoice: "friendly-expert",
count: 1 count: 1
}; };
// Simulated progress stages since the backend is a single call
const progressInterval = setInterval(() => {
setGenerationProgress(prev => {
if (prev >= 90) {
clearInterval(progressInterval);
return 90;
}
if (prev >= 60) setGenerationStage("Finalizing content and SEO...");
else if (prev >= 30) setGenerationStage("Generating platform-specific posts...");
return prev + 5;
});
}, 800);
const headers: HeadersInit = { 'Content-Type': 'application/json' }; const headers: HeadersInit = { 'Content-Type': 'application/json' };
if (session?.accessToken) { if (session?.accessToken) {
headers['Authorization'] = `Bearer ${session.accessToken}`; headers['Authorization'] = `Bearer ${session.accessToken}`;
@@ -157,29 +181,47 @@ export function GenerateWizard() {
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
clearInterval(progressInterval);
if (!response.ok) throw new Error("Generation failed"); if (!response.ok) throw new Error("Generation failed");
const data = await response.json(); setGenerationProgress(95);
setGeneratedBundle(data); setGenerationStage("Saving to library...");
toaster.create({ const data = await response.json();
title: "Content Generated",
description: "Your content is ready!", setGenerationProgress(100);
type: "success" setGenerationStage("Success!");
});
// Give a small delay to show 100%
setTimeout(() => {
setGeneratedBundle(data);
setIsGenerating(false);
toaster.create({
title: "Content Generated",
description: "Your content is ready and saved to library!",
type: "success"
});
// Auto-redirect after success
if (data.masterContentId) {
router.push(`/[locale]/content?id=${data.masterContentId}`);
}
}, 500);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setIsGenerating(false);
toaster.create({ toaster.create({
title: "Error", title: "Error",
description: "Failed to generate content. Please try again.", description: "Failed to generate content. Please try again.",
type: "error" type: "error"
}); });
} finally {
setIsGenerating(false);
} }
}; };
if (generatedBundle) { if (generatedBundle) {
return <GeneratedContentResult bundle={generatedBundle} onReset={() => { return <GeneratedContentResult bundle={generatedBundle} onReset={() => {
setGeneratedBundle(null); setGeneratedBundle(null);
@@ -204,7 +246,26 @@ export function GenerateWizard() {
</Box> </Box>
<Box p={6} borderWidth="1px" borderRadius="lg" bg="bg.panel"> <Box p={6} borderWidth="1px" borderRadius="lg" bg="bg.panel">
{activeStep === 0 && ( {isGenerating ? (
<VStack py={12} gap={6}>
<Spinner size="xl" color="blue.500" />
<VStack gap={2} width="full" maxW="md">
<Text fontWeight="bold" fontSize="lg">{generationStage}</Text>
<Progress.Root value={generationProgress} width="full" colorPalette="blue" shape="rounded" size="sm">
<Progress.Track>
<Progress.Range />
</Progress.Track>
</Progress.Root>
<Text fontSize="sm" color="fg.muted">{generationProgress}% Complete</Text>
</VStack>
<Text textAlign="center" color="fg.muted" maxW="xs">
Our AI is analyzing the topic, researching facts, and crafting perfect posts for your platforms.
</Text>
</VStack>
) : activeStep === 0 && (
<VStack align="stretch" gap={6}> <VStack align="stretch" gap={6}>
<Heading size="md">1. Start with a Topic</Heading> <Heading size="md">1. Start with a Topic</Heading>