From 143bd821e5dd1abb15522cfbf766763723dd0af4 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 5 Jan 2025 17:52:41 +0100 Subject: [PATCH] update --- app/Projects/[id]/page.tsx | 60 +++++++++++++++++++ app/api/stats/route.ts | 29 ++++++++++ app/components/Contact.tsx | 38 ++++++++++++ app/components/Footer.tsx | 18 ++++++ app/components/Header.tsx | 32 +++++++++++ app/components/Hero.tsx | 20 +++++++ app/components/Projects.tsx | 51 +++++++++++++++++ app/globals.css | 20 ++----- app/layout.tsx | 12 ++-- app/page.tsx | 111 ++++++------------------------------ app/stats/page.tsx | 89 +++++++++++++++++++++++++++++ package.json | 12 ++-- public/data/projects.json | 26 +++++++++ tailwind.config.ts | 16 +++--- 14 files changed, 404 insertions(+), 130 deletions(-) create mode 100644 app/Projects/[id]/page.tsx create mode 100644 app/api/stats/route.ts create mode 100644 app/components/Contact.tsx create mode 100644 app/components/Footer.tsx create mode 100644 app/components/Header.tsx create mode 100644 app/components/Hero.tsx create mode 100644 app/components/Projects.tsx create mode 100644 app/stats/page.tsx create mode 100644 public/data/projects.json diff --git a/app/Projects/[id]/page.tsx b/app/Projects/[id]/page.tsx new file mode 100644 index 0000000..9774b62 --- /dev/null +++ b/app/Projects/[id]/page.tsx @@ -0,0 +1,60 @@ +// app/Projects/[id]/page.tsx +"use client"; + +import { useParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import Link from "next/link"; + +interface Project { + id: string; + title: string; + description: string; + link: string; +} + +export default function ProjectDetail() { + const params = useParams(); + const { id } = params as { id: string }; + const [project, setProject] = useState(null); + + useEffect(() => { + if (id) { + fetch("/data/projects.json") + .then((res) => res.json()) + .then((data: Project[]) => { + const found = data.find((proj) => proj.id === id); + setProject(found || null); + + // Log the project view + fetch("/api/stats", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ type: "project_view", projectId: id }), + }).catch((err) => console.error("Failed to log project view", err)); + }); + } + }, [id]); + + if (!project) { + return
Loading...
; + } + + return ( +
+

+ {project.title} +

+

+ {project.description} +

+ + Visit Project + +
+ ); +} diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts new file mode 100644 index 0000000..cb50d5e --- /dev/null +++ b/app/api/stats/route.ts @@ -0,0 +1,29 @@ +// app/api/stats/route.ts +import { NextResponse } from "next/server"; + +const stats = { + views: 0, + projectsViewed: {} as { [key: string]: number }, +}; + +export async function GET() { + return NextResponse.json(stats); +} + +export async function POST(request: Request) { + const { type, projectId } = await request.json(); + + if (type === "page_view") { + stats.views += 1; + } + + if (type === "project_view" && projectId) { + if (stats.projectsViewed[projectId]) { + stats.projectsViewed[projectId] += 1; + } else { + stats.projectsViewed[projectId] = 1; + } + } + + return NextResponse.json({ message: "Stats updated", stats }); +} diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx new file mode 100644 index 0000000..16c8b8d --- /dev/null +++ b/app/components/Contact.tsx @@ -0,0 +1,38 @@ +// app/components/Contact.tsx +"use client"; + +export default function Contact() { + return ( +
+

+ Contact Me +

+
+ + + + +
+
+ ); +} diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx new file mode 100644 index 0000000..7536f72 --- /dev/null +++ b/app/components/Footer.tsx @@ -0,0 +1,18 @@ +// app/components/Footer.tsx + +import Link from "next/link"; + +export default function Footer() { + return ( +
+

Hi, this is a Footer

+

Maybe some social links here

+

© Dennis Konkol 2024

+ + + +
+ ); +} diff --git a/app/components/Header.tsx b/app/components/Header.tsx new file mode 100644 index 0000000..4571dbd --- /dev/null +++ b/app/components/Header.tsx @@ -0,0 +1,32 @@ +// app/components/Header.tsx +"use client"; + +import Link from "next/link"; + +export default function Header() { + return ( +
+ +
+ ); +} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx new file mode 100644 index 0000000..c41013b --- /dev/null +++ b/app/components/Hero.tsx @@ -0,0 +1,20 @@ +// app/components/Hero.tsx + +import Link from "next/link"; + +export default function Hero() { + return ( +
+

Hi, I am Dennis

+

A student

+ + + +
+ ); +} diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx new file mode 100644 index 0000000..72a41dc --- /dev/null +++ b/app/components/Projects.tsx @@ -0,0 +1,51 @@ +// app/components/Projects.tsx +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface Project { + id: string; + title: string; + description: string; + link: string; +} + +export default function Projects() { + const [projects, setProjects] = useState([]); + + useEffect(() => { + fetch("/data/projects.json") + .then((res) => res.json()) + .then((data) => setProjects(data)); + }, []); + + return ( +
+

+ Projects +

+
+ {projects.map((project) => ( +
+

+ {project.title} +

+

+ {project.description} +

+ + View Project + +
+ ))} +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index 6b717ad..fbe1105 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,21 +1,11 @@ +/* app/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - +/* Global Dark Mode Styles */ body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; + color: #ededed; /* Foreground color */ + background: #0a0a0a; /* Background color */ + font-family: Arial, Helvetica, sans-serif; } diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..b957c5b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,3 +1,5 @@ +// app/layout.tsx + import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; @@ -13,17 +15,17 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Dennis's Portfolio", + description: "A portfolio website showcasing my work and skills.", }; export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode; -}>) { +}) { return ( - + diff --git a/app/page.tsx b/app/page.tsx index 9007252..5594fc5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,101 +1,22 @@ -import Image from "next/image"; +// app/page.tsx +"use client"; + +import Header from "./components/Header"; +import Hero from "./components/Hero"; +import Projects from "./components/Projects"; +import Contact from "./components/Contact"; +import Footer from "./components/Footer"; export default function Home() { return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
- - + <> +
+
+ + + +
- -
+ ); } diff --git a/app/stats/page.tsx b/app/stats/page.tsx new file mode 100644 index 0000000..75056bf --- /dev/null +++ b/app/stats/page.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useState } from "react"; + +export default function StatsDashboard() { + const [authenticated, setAuthenticated] = useState(false); + const [password, setPassword] = useState(""); + const [stats, setStats] = useState<{ + views: number; + projectsViewed: Record; + } | null>(null); + const [error, setError] = useState(""); + + const handleLogin = () => { + // Simple password check. Replace with secure authentication. + if (password === "admin123") { + setAuthenticated(true); + fetchStats(); + } else { + setError("Incorrect password."); + } + }; + + const fetchStats = async () => { + try { + const res = await fetch("/api/stats"); + const data = await res.json(); + setStats(data); + } catch (err) { + console.error(err); + setError("Failed to fetch stats."); + } + }; + + if (!authenticated) { + return ( +
+
+

+ Admin Login +

+ {error &&

{error}

} + setPassword(e.target.value)} + /> + +
+
+ ); + } + + return ( +
+

+ Statistics Dashboard +

+ {stats ? ( +
+

+ Total Views: {stats.views} +

+

+ Project Views: +

+
    + {Object.entries(stats.projectsViewed).map(([projectId, count]) => ( +
  • + Project ID {projectId}: {count} view{count !== 1 ? "s" : ""} +
  • + ))} +
+
+ ) : ( +

+ Loading statistics... +

+ )} +
+ ); +} diff --git a/package.json b/package.json index 3020e1c..9743f6b 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,19 @@ "lint": "next lint" }, "dependencies": { + "next": "15.1.3", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.1.3" + "react-dom": "^19.0.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "postcss": "^8", - "tailwindcss": "^3.4.1", "eslint": "^9", "eslint-config-next": "15.1.3", - "@eslint/eslintrc": "^3" + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" } } diff --git a/public/data/projects.json b/public/data/projects.json new file mode 100644 index 0000000..28ee002 --- /dev/null +++ b/public/data/projects.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "title": "Project One", + "description": "A brief description of Project One.", + "link": "/Projects/1" + }, + { + "id": "2", + "title": "Project Two", + "description": "A brief description of Project Two.", + "link": "/Projects/2" + }, + { + "id": "3", + "title": "Project Three", + "description": "A brief description of Project Three.", + "link": "/Projects/3" + }, + { + "id": "4", + "title": "Project Marie/Dennis", + "description": "Ich liebe Marie", + "link": "/Projects/4" + } +] diff --git a/tailwind.config.ts b/tailwind.config.ts index 1362b88..bf7a249 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,18 +1,16 @@ +// tailwind.config.ts import type { Config } from "tailwindcss"; +// tailwind.config.js export default { + darkMode: "class", // Enables class-based dark mode content: [ - "./pages/**/*.{js,ts,jsx,tsx,mdx}", - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx}", + "./app/components/**/*.{js,ts,jsx,tsx}", + // Add other paths if necessary ], theme: { - extend: { - colors: { - background: "var(--background)", - foreground: "var(--foreground)", - }, - }, + extend: {}, }, plugins: [], } satisfies Config;