diff --git a/.env b/.env new file mode 100644 index 0000000..45b298f --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ + +# NextAuth Configuration +NEXTAUTH_URL=http://localhost:3001 +NEXTAUTH_SECRET=dev_secret_key_change_in_production + +# Backend API URL +NEXT_PUBLIC_API_URL=http://localhost:3000/api + +# Auth Mode +NEXT_PUBLIC_AUTH_REQUIRED=false + +# Third Party Keys (Placeholders) +NEXT_PUBLIC_GOOGLE_API_KEY='test-key' diff --git a/messages/ar.json b/messages/ar.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/ar.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/de.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/es.json b/messages/es.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/es.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/fr.json b/messages/fr.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/fr.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/ja.json b/messages/ja.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/ja.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/pt.json b/messages/pt.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/pt.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/ru.json b/messages/ru.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/ru.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/messages/zh.json b/messages/zh.json new file mode 100644 index 0000000..390ae21 --- /dev/null +++ b/messages/zh.json @@ -0,0 +1,32 @@ +{ + "home": "Home", + "about": "About", + "solutions": "Solutions", + "intelligent-transportation-systems": "Intelligent Transportation Systems", + "artificial-intelligence": "Artificial Intelligence", + "error": { + "not-found": "Oops! The page you’re looking for doesn’t exist.", + "404": "404", + "back-to-home": "Go back home" + }, + "email": "E-Mail", + "password": "Password", + "auth": { + "remember-me": "Remember Me", + "dont-have-account": "Don't have an account?", + "sign-out": "Sign Out", + "sign-up": "Sign Up", + "sign-in": "Sign In", + "welcome-back": "Welcome Back", + "subtitle": "Enter your email and password to sign in", + "already-have-an-account": "Already have an account?", + "create-an-account-now": "Create an account now" + }, + "all-right-reserved": "All rights reserved.", + "privacy-policy": "Privacy Policy", + "terms-of-service": "Terms of Service", + "name": "Name", + "low": "Low", + "medium": "Medium", + "high": "High" +} \ No newline at end of file diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts index 101586f..b9be33d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -11,7 +11,7 @@ const nextConfig: NextConfig = { return [ { source: "/api/backend/:path*", - destination: "http://localhost:3000/api/:path*", + destination: "http://localhost:3001/api/:path*", }, ]; }, diff --git a/package-lock.json b/package-lock.json index d0120d5..f738a8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -146,7 +146,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -556,7 +555,6 @@ "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.31.0.tgz", "integrity": "sha512-puvrZOfnfMA+DckDcz0UxO20l7TVhwsdQ9ksCv4nIUB430yuWzon0yo9fM10lEr3hd7BhjZARpMCVw5u280clw==", "license": "MIT", - "peer": true, "dependencies": { "@ark-ui/react": "^5.29.1", "@emotion/is-prop-valid": "^1.4.0", @@ -692,7 +690,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1986,7 +1983,6 @@ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/helpers": "^0.5.0" } @@ -2905,7 +2901,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -3067,7 +3062,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3127,7 +3121,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -4555,7 +4548,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4921,7 +4913,6 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -5028,7 +5019,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5781,7 +5771,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5983,7 +5972,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7935,7 +7923,6 @@ "integrity": "sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg==", "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "16.0.0", "@swc/helpers": "0.5.15", @@ -8599,7 +8586,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -8747,7 +8733,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8757,7 +8742,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8770,7 +8754,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -9683,7 +9666,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9864,7 +9846,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10319,7 +10300,6 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/[locale]/(dashboard)/analytics/page.tsx b/src/app/[locale]/(dashboard)/analytics/page.tsx new file mode 100644 index 0000000..9deb64e --- /dev/null +++ b/src/app/[locale]/(dashboard)/analytics/page.tsx @@ -0,0 +1,22 @@ +import { getTranslations } from "next-intl/server"; +import { Box, Heading, Text, Center } from "@chakra-ui/react"; +import { LuTrendingUp } from "react-icons/lu"; + +export async function generateMetadata() { + const t = await getTranslations(); + return { + title: `Analytics | Content Hunter`, + }; +} + +export default function AnalyticsPage() { + return ( + + Analytics & Insights +
+ + Analytics dashboard coming soon. +
+
+ ); +} diff --git a/src/app/[locale]/(dashboard)/content/page.tsx b/src/app/[locale]/(dashboard)/content/page.tsx new file mode 100644 index 0000000..cc2cd37 --- /dev/null +++ b/src/app/[locale]/(dashboard)/content/page.tsx @@ -0,0 +1,19 @@ +import { getTranslations } from "next-intl/server"; +import { Box, Heading } from "@chakra-ui/react"; +import { ContentTable } from "@/components/content/ContentTable"; + +export async function generateMetadata() { + const t = await getTranslations(); + return { + title: `Content | Content Hunter`, + }; +} + +export default function ContentPage() { + return ( + + Content Management + + + ); +} diff --git a/src/app/[locale]/(dashboard)/generate/page.tsx b/src/app/[locale]/(dashboard)/generate/page.tsx new file mode 100644 index 0000000..7982693 --- /dev/null +++ b/src/app/[locale]/(dashboard)/generate/page.tsx @@ -0,0 +1,22 @@ +import { Box, Heading } from "@chakra-ui/react"; +import { GenerateWizard } from "@/components/generate/GenerateWizard"; +import { getTranslations } from "next-intl/server"; +import { Suspense } from "react"; + +export async function generateMetadata() { + const t = await getTranslations(); + return { + title: `Generate Content | Content Hunter`, + }; +} + +export default function GeneratePage() { + return ( + + Content Generator + Loading...}> + + + + ); +} diff --git a/src/app/[locale]/(dashboard)/home/page.tsx b/src/app/[locale]/(dashboard)/home/page.tsx new file mode 100644 index 0000000..f8abc10 --- /dev/null +++ b/src/app/[locale]/(dashboard)/home/page.tsx @@ -0,0 +1,67 @@ +import { getTranslations } from "next-intl/server"; +import { Box, Heading, Text, SimpleGrid, Card } from "@chakra-ui/react"; +import { LuFileText, LuCalendar, LuTrendingUp } from "react-icons/lu"; +import { Link } from '@/i18n/navigation'; + +export async function generateMetadata() { + const t = await getTranslations(); + + return { + title: `${t("home")} | Content Hunter`, + }; +} + +export default function Home() { + return ( + <> + + Welcome back, Hunter! + Here's what's happening with your content today. + + + + + + + + + + Content Pieces + + You have 12 active content pieces in your pipeline. + + + + + + + + + + + + Scheduled Posts + + 5 posts are scheduled for meaningful engagement today. + + + + + + + + + + + + Analytics + + Your engagement rate increased by 15% this week. + + + + + + + ); +} diff --git a/src/app/[locale]/(dashboard)/layout.tsx b/src/app/[locale]/(dashboard)/layout.tsx new file mode 100644 index 0000000..835e9f8 --- /dev/null +++ b/src/app/[locale]/(dashboard)/layout.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { DashboardLayout } from '@/components/layout/dashboard/DashboardLayout'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/src/app/[locale]/(dashboard)/schedule/page.tsx b/src/app/[locale]/(dashboard)/schedule/page.tsx new file mode 100644 index 0000000..9a9b52e --- /dev/null +++ b/src/app/[locale]/(dashboard)/schedule/page.tsx @@ -0,0 +1,22 @@ +import { getTranslations } from "next-intl/server"; +import { Box, Heading, Text, Center } from "@chakra-ui/react"; +import { LuCalendar } from "react-icons/lu"; + +export async function generateMetadata() { + const t = await getTranslations(); + return { + title: `Schedule | Content Hunter`, + }; +} + +export default function SchedulePage() { + return ( + + Schedule & Calendar +
+ + Calendar and scheduling tools coming soon. +
+
+ ); +} diff --git a/src/app/[locale]/(dashboard)/settings/page.tsx b/src/app/[locale]/(dashboard)/settings/page.tsx new file mode 100644 index 0000000..1d95b51 --- /dev/null +++ b/src/app/[locale]/(dashboard)/settings/page.tsx @@ -0,0 +1,22 @@ +import { getTranslations } from "next-intl/server"; +import { Box, Heading, Text, Center } from "@chakra-ui/react"; +import { LuSettings } from "react-icons/lu"; + +export async function generateMetadata() { + const t = await getTranslations(); + return { + title: `Settings | Content Hunter`, + }; +} + +export default function SettingsPage() { + return ( + + Settings +
+ + Global application settings coming soon. +
+
+ ); +} diff --git a/src/app/[locale]/(dashboard)/source-accounts/page.tsx b/src/app/[locale]/(dashboard)/source-accounts/page.tsx new file mode 100644 index 0000000..ee9cfa8 --- /dev/null +++ b/src/app/[locale]/(dashboard)/source-accounts/page.tsx @@ -0,0 +1,321 @@ +"use client"; + +import { Box, Heading, VStack, Text, Card, HStack, Badge, Button, Input, SimpleGrid, Spinner, Icon, Table, IconButton, Dialog, Field } from "@chakra-ui/react"; +import { LuPlus, LuTrash, LuLink, LuRefreshCw, LuSparkles, LuEye, LuTrendingUp, LuUser } from "react-icons/lu"; +import { toaster } from "@/components/ui/feedback/toaster"; +import { useState, useEffect } from "react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "@/i18n/navigation"; + +interface SourceAccount { + id: string; + platform: string; + username: string; + url: string; + followersCount?: number; + postsAnalyzed?: number; + averageEngagement?: number; + lastAnalyzed?: string; + status: string; +} + +interface AnalyzedPost { + id: string; + content: string; + engagement: number; + likes: number; + comments: number; + postedAt: string; + viralScore?: number; +} + +export default function SourceAccountsPage() { + const { data: session } = useSession(); + const router = useRouter(); + const [accounts, setAccounts] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isAdding, setIsAdding] = useState(false); + const [showAddModal, setShowAddModal] = useState(false); + const [newAccountUrl, setNewAccountUrl] = useState(""); + const [selectedAccount, setSelectedAccount] = useState(null); + const [posts, setPosts] = useState([]); + + useEffect(() => { + fetchAccounts(); + }, []); + + const getHeaders = (): HeadersInit => { + const headers: HeadersInit = { 'Content-Type': 'application/json' }; + if (session?.accessToken) { + headers['Authorization'] = `Bearer ${session.accessToken}`; + } + return headers; + }; + + const fetchAccounts = async () => { + try { + const res = await fetch('/api/backend/source-accounts', { headers: getHeaders() }); + if (res.ok) { + const data = await res.json(); + setAccounts(Array.isArray(data) ? data : []); + } + } catch (error) { + console.error('Fetch error:', error); + } finally { + setIsLoading(false); + } + }; + + const handleAddAccount = async () => { + if (!newAccountUrl.trim()) { + toaster.create({ title: "Please enter a URL", type: "warning" }); + return; + } + + setIsAdding(true); + try { + const res = await fetch('/api/backend/source-accounts', { + method: 'POST', + headers: getHeaders(), + body: JSON.stringify({ url: newAccountUrl }), + }); + + if (res.ok) { + toaster.create({ title: "Source account added", type: "success" }); + setNewAccountUrl(""); + setShowAddModal(false); + fetchAccounts(); + } else { + throw new Error('Failed to add account'); + } + } catch (error) { + toaster.create({ title: "Failed to add account", type: "error" }); + } finally { + setIsAdding(false); + } + }; + + const handleDeleteAccount = async (id: string) => { + try { + const res = await fetch(`/api/backend/source-accounts/${id}`, { + method: 'DELETE', + headers: getHeaders(), + }); + + if (res.ok) { + toaster.create({ title: "Account removed", type: "success" }); + setAccounts(accounts.filter(a => a.id !== id)); + } + } catch (error) { + toaster.create({ title: "Failed to remove account", type: "error" }); + } + }; + + const handleAnalyze = async (account: SourceAccount) => { + toaster.create({ title: "Analyzing account...", type: "info" }); + try { + const res = await fetch(`/api/backend/source-accounts/${account.id}/analyze`, { + method: 'POST', + headers: getHeaders(), + }); + + if (res.ok) { + toaster.create({ title: "Analysis complete", type: "success" }); + fetchAccounts(); + } + } catch (error) { + toaster.create({ title: "Analysis failed", type: "error" }); + } + }; + + const handleViewPosts = async (account: SourceAccount) => { + setSelectedAccount(account); + try { + const res = await fetch(`/api/backend/source-accounts/${account.id}/posts`, { + headers: getHeaders() + }); + if (res.ok) { + const data = await res.json(); + setPosts(Array.isArray(data) ? data : []); + } + } catch (error) { + console.error('Error fetching posts:', error); + } + }; + + const handleInspireContent = (post?: AnalyzedPost) => { + const topic = post?.content?.slice(0, 100) || selectedAccount?.username; + router.push(`/generate?topic=${encodeURIComponent(topic || '')}&mode=inspire`); + }; + + return ( + + {/* Header */} + + + Kaynak Hesaplar + Başarılı hesapları takip et ve içerik ilhamı al + + + + + {/* Accounts Grid */} + {isLoading ? ( + + ) : accounts.length > 0 ? ( + + {accounts.map((account) => ( + + + + + + @{account.username} + + + {account.platform} + + + + + + + + {account.followersCount?.toLocaleString() || 0} takipçi + + + {account.postsAnalyzed || 0} post analiz + + + + {account.averageEngagement && ( + + Avg. Engagement: {account.averageEngagement.toFixed(1)}% + + )} + + + + + handleDeleteAccount(account.id)} + aria-label="Delete" + > + + + + + + + ))} + + ) : ( + + + + + + Henüz kaynak hesap eklenmedi.
+ Başarılı hesapları ekleyerek içerik ilhamı alın. +
+ +
+
+
+ )} + + {/* Posts Modal */} + {selectedAccount && ( + setSelectedAccount(null)}> + + + + + @{selectedAccount.username} Postları + + + + {posts.map((post) => ( + + + + {post.content} + + + ❤️ {post.likes} + 💬 {post.comments} + {post.viralScore && ( + + 🔥 Viral: {post.viralScore} + + )} + + + + + + + ))} + {posts.length === 0 && ( + + Henüz analiz edilmiş post yok + + )} + + + + + + + + + )} + + {/* Add Account Modal */} + setShowAddModal(false)}> + + + + + Kaynak Hesap Ekle + + + + + Hesap URL'si + setNewAccountUrl(e.target.value)} + /> + Twitter, Instagram, LinkedIn veya TikTok hesap linki + + + + + + + + + + +
+ ); +} diff --git a/src/app/[locale]/(dashboard)/trends/page.tsx b/src/app/[locale]/(dashboard)/trends/page.tsx new file mode 100644 index 0000000..95533ab --- /dev/null +++ b/src/app/[locale]/(dashboard)/trends/page.tsx @@ -0,0 +1,371 @@ +"use client"; + +import { Box, Heading, VStack, Text, Card, HStack, Badge, Button, Input, SimpleGrid, Spinner, Icon } from "@chakra-ui/react"; +import { LuTrendingUp, LuSearch, LuSparkles, LuRefreshCw, LuArrowRight, LuGlobe, LuHash, LuZoomIn } from "react-icons/lu"; +import { toaster } from "@/components/ui/feedback/toaster"; +import { useState } from "react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "@/i18n/navigation"; + +interface Trend { + id: string; + title: string; + description?: string; + score: number; + volume?: number; + source: string; + keywords: string[]; + relatedTopics: string[]; + url?: string; +} + +export default function TrendsPage() { + const { data: session } = useSession(); + const router = useRouter(); + const [niche, setNiche] = useState(""); + const [trends, setTrends] = useState([]); + const [isScanning, setIsScanning] = useState(false); + const [isDeepScanning, setIsDeepScanning] = useState(false); + const [selectedTrend, setSelectedTrend] = useState(null); + const [scanCount, setScanCount] = useState(0); + const [translatingId, setTranslatingId] = useState(null); + const [translatedContent, setTranslatedContent] = useState>({}); + + const handleScanTrends = async () => { + if (!niche.trim()) { + toaster.create({ title: "Please enter a niche or topic", type: "warning" }); + return; + } + + setIsScanning(true); + try { + const headers: HeadersInit = { 'Content-Type': 'application/json' }; + if (session?.accessToken) { + headers['Authorization'] = `Bearer ${session.accessToken}`; + } + + const res = await fetch('/api/backend/trends/scan', { + method: 'POST', + headers, + body: JSON.stringify({ + keywords: [niche], + country: 'TR', + }), + }); + + if (res.ok) { + const response = await res.json(); + // Backend wraps response in { success, status, message, data } + const trendsData = response.data || response.trends || response; + const trendsArray = Array.isArray(trendsData) ? trendsData : []; + setTrends(trendsArray); + setScanCount(1); + toaster.create({ + title: `${trendsArray.length} trend bulundu`, + type: trendsArray.length > 0 ? "success" : "info" + }); + } else { + throw new Error('Failed to scan trends'); + } + } catch (error) { + console.error('Scan error:', error); + toaster.create({ title: "Failed to scan trends", type: "error" }); + } finally { + setIsScanning(false); + } + }; + + // DEEPER RESEARCH - Prepends new results and uses all languages + const handleDeepResearch = async () => { + if (!niche.trim()) { + toaster.create({ title: "Please enter a niche or topic first", type: "warning" }); + return; + } + + setIsDeepScanning(true); + try { + const headers: HeadersInit = { 'Content-Type': 'application/json' }; + if (session?.accessToken) { + headers['Authorization'] = `Bearer ${session.accessToken}`; + } + + // Use different keywords for deeper research + const deepKeywords = [ + niche, + `${niche} trends`, + `${niche} news`, + `latest ${niche}`, + ]; + + const res = await fetch('/api/backend/trends/scan', { + method: 'POST', + headers, + body: JSON.stringify({ + keywords: deepKeywords, + country: 'TR', + allLanguages: true, // Fetch global trends + }), + }); + + if (res.ok) { + const response = await res.json(); + const trendsData = response.data || response.trends || response; + const newTrends = Array.isArray(trendsData) ? trendsData : []; + + // Prepend new trends, filter duplicates by title + const existingTitles = new Set(trends.map(t => t.title.toLowerCase())); + const uniqueNewTrends = newTrends.filter( + (t: Trend) => !existingTitles.has(t.title.toLowerCase()) + ); + + setTrends(prev => [...uniqueNewTrends, ...prev]); + setScanCount(prev => prev + 1); + toaster.create({ + title: `${uniqueNewTrends.length} yeni trend eklendi (Üste eklendi)`, + description: `Toplam: ${trends.length + uniqueNewTrends.length} trend`, + type: uniqueNewTrends.length > 0 ? "success" : "info" + }); + } else { + throw new Error('Failed to deep scan'); + } + } catch (error) { + console.error('Deep scan error:', error); + toaster.create({ title: "Derin araştırma başarısız", type: "error" }); + } finally { + setIsDeepScanning(false); + } + }; + + const handleTranslate = async (trend: Trend) => { + if (translatedContent[trend.id]) return; + + setTranslatingId(trend.id); + try { + const headers: HeadersInit = { 'Content-Type': 'application/json' }; + if (session?.accessToken) { + headers['Authorization'] = `Bearer ${session.accessToken}`; + } + + // Translate title and description together for efficiency + const textToTranslate = `${trend.title}\n---\n${trend.description || ""}`; + + const res = await fetch('/api/backend/trends/translate', { + method: 'POST', + headers, + body: JSON.stringify({ + text: textToTranslate, + targetLanguage: 'Turkish', + }), + }); + + if (res.ok) { + const responseData = await res.json(); + // Handle potential 'data' wrapping from NestJS interceptors + const data = responseData.data || responseData; + const translatedText = data.translatedText; + + if (translatedText && typeof translatedText === 'string') { + const [newTitle, ...descParts] = translatedText.split('\n---\n'); + + setTranslatedContent(prev => ({ + ...prev, + [trend.id]: { + title: newTitle.trim(), + description: descParts.join('\n---\n').trim(), + } + })); + } else { + throw new Error('Invalid translation response'); + } + } + } catch (error) { + console.error('Translation error:', error); + toaster.create({ title: "Çeviri başarısız", type: "error" }); + } finally { + setTranslatingId(null); + } + }; + + const handleCreateContent = (trend: Trend) => { + // Navigate to generate page with pre-filled trend data + const params = new URLSearchParams(); + params.set('topic', trend.title); + if (trend.description) { + params.set('description', trend.description); + } + if (trend.keywords && trend.keywords.length > 0) { + params.set('keywords', JSON.stringify(trend.keywords)); + } + if (trend.source) { + params.set('source', trend.source); + } + router.push(`/generate?${params.toString()}`); + }; + + return ( + + {/* Header */} + + + Trend Araştırması + Güncel trendleri keşfet ve içerik oluştur + + + + {/* Search Box */} + + + + + setNiche(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleScanTrends()} + flex={1} + /> + + + + Google Trends, Twitter, Reddit ve haber kaynaklarından güncel trendleri tarar + + + + + + {/* Deeper Research Button - Shows after initial scan */} + {trends.length > 0 && ( + + + {trends.length} trend bulundu (Tarama #{scanCount}) + + + + )} + + {/* Results */} + {trends.length > 0 && ( + + {trends.map((trend) => ( + setSelectedTrend(trend)} + _hover={{ shadow: "md" }} + > + + + + {Math.round(trend.score)} + + {trend.source} + + + + + + {translatedContent[trend.id]?.title || trend.title} + + {(translatedContent[trend.id]?.description || trend.description) && ( + + {translatedContent[trend.id]?.description || trend.description} + + )} + {translatedContent[trend.id] && ( + + AI Çeviri + + )} + + {trend.keywords.length > 0 && ( + + {trend.keywords.slice(0, 3).map((kw, i) => ( + + {kw} + + ))} + + )} + + + + {trend.url && ( + + )} + {!translatedContent[trend.id] && trend.relatedTopics?.some(t => ['EN', 'DE'].includes(t)) && ( + + )} + + + + + + + ))} + + )} + + {/* Empty State */} + {!isScanning && trends.length === 0 && ( + + + + + + Henüz trend taraması yapılmadı.
+ Bir niş veya konu girerek başlayın. +
+
+
+
+ )} +
+ ); +} diff --git a/src/app/[locale]/(dashboard)/video/page.tsx b/src/app/[locale]/(dashboard)/video/page.tsx new file mode 100644 index 0000000..28c5caa --- /dev/null +++ b/src/app/[locale]/(dashboard)/video/page.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { Box, Heading, VStack, Text, Card, HStack, Badge, Button, Input, SimpleGrid, Spinner, Icon, Tabs, Textarea } from "@chakra-ui/react"; +import { LuVideo, LuImage, LuDownload, LuSparkles, LuRefreshCw, LuCopy } from "react-icons/lu"; +import { toaster } from "@/components/ui/feedback/toaster"; +import { useState } from "react"; +import { useSession } from "next-auth/react"; + +interface GeneratedAsset { + id: string; + type: 'image' | 'video' | 'thumbnail'; + url: string; + prompt: string; + createdAt: string; +} + +export default function VideoPage() { + const { data: session } = useSession(); + const [topic, setTopic] = useState(""); + const [style, setStyle] = useState("professional"); + const [isGenerating, setIsGenerating] = useState(false); + const [generatedAssets, setGeneratedAssets] = useState([]); + const [activeTab, setActiveTab] = useState("thumbnail"); + + const getHeaders = (): HeadersInit => { + const headers: HeadersInit = { 'Content-Type': 'application/json' }; + if (session?.accessToken) { + headers['Authorization'] = `Bearer ${session.accessToken}`; + } + return headers; + }; + + const handleGenerate = async (type: 'thumbnail' | 'video') => { + if (!topic.trim()) { + toaster.create({ title: "Please enter a topic", type: "warning" }); + return; + } + + setIsGenerating(true); + try { + // Use correct backend endpoints + const endpoint = type === 'thumbnail' + ? '/api/backend/video-thumbnail/thumbnails/generate' + : '/api/backend/video-thumbnail/package'; + + const body = type === 'thumbnail' + ? { + title: topic, + videoType: style, + pattern: 'face_text', + keyMessage: topic, + } + : { + topic, + platform: 'youtube', + format: 'short', + targetAudience: 'general', + }; + + const res = await fetch(endpoint, { + method: 'POST', + headers: getHeaders(), + body: JSON.stringify(body), + }); + + if (res.ok) { + const data = await res.json(); + // Extract URL from the response - thumbnail returns prompt, not URL + const assetUrl = data.url || data.thumbnailUrl || data.videoUrl || ''; + const promptData = data.prompt || data.thumbnail?.prompt || topic; + + setGeneratedAssets(prev => [{ + id: `asset-${Date.now()}`, + type, + url: assetUrl || `/api/placeholder/${type}`, // Fallback + prompt: typeof promptData === 'string' ? promptData : topic, + createdAt: new Date().toISOString(), + }, ...prev]); + toaster.create({ title: `${type === 'thumbnail' ? 'Thumbnail' : 'Video'} package generated!`, type: "success" }); + } else { + const error = await res.json(); + throw new Error(error.message || 'Generation failed'); + } + } catch (error: any) { + console.error('Generation error:', error); + toaster.create({ title: error.message || "Generation failed", type: "error" }); + } finally { + setIsGenerating(false); + } + }; + + const handleCopyPrompt = (prompt: string) => { + navigator.clipboard.writeText(prompt); + toaster.create({ title: "Prompt copied", type: "success" }); + }; + + const handleDownload = async (url: string, filename: string) => { + try { + const response = await fetch(url); + const blob = await response.blob(); + const downloadUrl = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = downloadUrl; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(downloadUrl); + } catch (error) { + toaster.create({ title: "Download failed", type: "error" }); + } + }; + + return ( + + {/* Header */} + + + Video & Thumbnail + AI ile görsel ve video oluştur + + + + {/* Generator Card */} + + + setActiveTab(e.value)}> + + + Thumbnail + + + Video + + + + +