Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions fullstack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
11 changes: 11 additions & 0 deletions fullstack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
This is a [Next.js](https://nextjs.org) project created with `create-next-app`.

## Getting Started

First, run the development server:

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 changes: 18 additions & 0 deletions fullstack/app/api/posts/[id]/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getPostById, posts } from "@/data/posts";

export function generateStaticParams() {
return posts.map((post) => ({
id: String(post.id),
}));
}

export async function GET(request, { params }) {
const { id } = await params;
const post = getPostById(id);

if (!post) {
return Response.json({ error: "Post not found" }, { status: 404 });
}

return Response.json(post);
}
5 changes: 5 additions & 0 deletions fullstack/app/api/posts/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { posts } from "@/data/posts";

export async function GET() {
return Response.json(posts);
}
43 changes: 43 additions & 0 deletions fullstack/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import "tailwindcss";

:root {
--background: #f8fafc;
--foreground: #0f172a;
--muted: #64748b;
--accent: #14b8a6;
--border: #e2e8f0;
--card: #ffffff;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-inter);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #020617;
--foreground: #f8fafc;
--muted: #94a3b8;
--accent: #2dd4bf;
--border: #1e293b;
--card: #0f172a;
}
}

* {
box-sizing: border-box;
}

body {
margin: 0;
background: var(--background);
color: var(--foreground);
font-family: var(--font-inter), Arial, Helvetica, sans-serif;
text-rendering: optimizeLegibility;
}

a {
text-decoration: none;
}
21 changes: 21 additions & 0 deletions fullstack/app/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
display: "swap",
});

export const metadata = {
title: "Fullstack Blog",
description: "A simple blog built with Next.js",
};

export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
);
}
64 changes: 64 additions & 0 deletions fullstack/app/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Link from "next/link";
import { posts } from "@/data/posts";

export default function HomePage() {
return (
<main className="min-h-screen overflow-hidden bg-[#F8FAFC] px-5 py-12 text-[#0F172A] dark:bg-slate-950 dark:text-slate-50 sm:px-8 lg:px-10">
<section className="relative mx-auto flex max-w-[800px] flex-col items-center pb-14 pt-10 text-center sm:pt-16">
<div className="absolute inset-x-[-20%] top-0 -z-10 h-80 bg-[radial-gradient(circle_at_top,#CCFBF1_0%,rgba(204,251,241,0.55)_30%,rgba(248,250,252,0)_68%)] dark:bg-[radial-gradient(circle_at_top,rgba(20,184,166,0.22)_0%,rgba(15,23,42,0.2)_35%,rgba(2,6,23,0)_70%)]" />
<p className="rounded-full border border-[#99F6E4] bg-white/70 px-4 py-1.5 text-xs font-bold uppercase tracking-[0.18em] text-[#0F766E] shadow-sm backdrop-blur dark:border-teal-400/20 dark:bg-slate-900/70 dark:text-teal-300">
THE POWER OF NEXT.JS
</p>
<h1 className="mt-7 text-5xl font-extrabold tracking-tight text-[#0F172A] dark:text-white sm:text-6xl">
Learn Next.js with JPDEV!
</h1>
<div className="mt-5 h-1 w-24 origin-center animate-[pulse_3s_ease-in-out_infinite] rounded-full bg-[#14B8A6]" />
<p className="mt-6 max-w-2xl text-lg leading-8 text-[#64748B] dark:text-slate-300 sm:text-xl">
A calm, content-first tech blog built with Next.js, featuring readable
articles.
</p>
</section>

<section className="mx-auto grid max-w-[800px] gap-6 pb-16">
<div className="flex items-end justify-between gap-4 border-b border-[#E2E8F0] pb-5 dark:border-slate-800">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.16em] text-[#14B8A6]">
Latest Posts
</p>
<h2 className="mt-2 text-2xl font-bold tracking-tight">
Notes from the stack
</h2>
</div>
<p className="hidden text-sm text-[#64748B] dark:text-slate-400 sm:block">
{posts.length} articles
</p>
</div>

<div className="grid gap-5">
{posts.map((post) => (
<article
key={post.id}
className="group rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-[0_1px_2px_rgba(15,23,42,0.04),0_12px_30px_rgba(15,23,42,0.06)] transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_18px_45px_rgba(15,23,42,0.12)] dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/20 dark:hover:border-teal-400/30 sm:p-8"
>
<p className="text-sm font-medium text-[#64748B] dark:text-slate-400">
{post.date}
</p>
<h3 className="mt-3 text-2xl font-bold tracking-tight text-[#0F172A] transition-colors duration-300 group-hover:text-[#0F172A]">
{post.title}
</h3>
<p className="mt-4 max-w-2xl text-base leading-7 text-[#64748B] dark:text-slate-300">
{post.excerpt}
</p>
<Link
href={`/posts/${post.id}`}
className="mt-6 inline-flex items-center font-semibold text-[#0F766E] transition-colors duration-300 hover:text-[#14B8A6] dark:text-teal-300 dark:hover:text-teal-200"
>
Read Article &rarr;
</Link>
</article>
))}
</div>
</section>
</main>
);
}
34 changes: 34 additions & 0 deletions fullstack/app/posts/[id]/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Link from "next/link";
import { notFound } from "next/navigation";
import { getPostById, posts } from "@/data/posts";

export function generateStaticParams() {
return posts.map((post) => ({
id: String(post.id),
}));
}

export default async function BlogPostPage({ params }) {
const { id } = await params;
const post = getPostById(id);

if (!post) {
notFound();
}

return (
<main className="grid gap-5 px-5 py-12 text-[#0F172A] dark:bg-slate-950 dark:text-slate-50 sm:px-8 lg:px-10">
<article className="mx-auto max-w-3xl rounded-lg border border-slate-200 bg-white p-8 shadow-sm">
<Link
href="/"
className="font-medium text-teal-700 hover:text-teal-900"
>
Back to posts
</Link>
<p className="mt-8 text-sm text-slate-500">{post.date}</p>
<h1 className="mt-3 text-4xl font-bold text-[#0F172A]">{post.title}</h1>
<p className="mt-6 text-lg leading-8 text-slate-700">{post.content}</p>
</article>
</main>
);
}
38 changes: 38 additions & 0 deletions fullstack/data/posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const posts = [
{
id: 1,
title: "Getting Started with Next.js",
excerpt: "Learn the basics of Next.js and how to create your first app.",
content:
"Next.js is a React framework that helps you build fast web applications with file-based routing, server rendering, and API routes. In this simple blog, each page is created from files inside the app folder.",
date: "2025-04-15",
},
{
id: 2,
title: "Styling in Next.js",
excerpt: "Different ways to style your Next.js application.",
content:
"There are multiple ways to style a Next.js application, including global CSS, CSS modules, and Tailwind CSS. This project uses Tailwind utility classes for simple layout, spacing, and colors.",
date: "2025-04-16",
},
{
id: 3,
title: "Building API Routes",
excerpt: "Serve blog data from a small API endpoint.",
content:
"Route handlers let a Next.js app return JSON from files inside the app/api folder. This makes it easy to expose local data, such as blog posts, without setting up a database for a small assessment project.",
date: "2025-04-17",
},
{
id: 4,
title: "Why File-Based Routing Helps",
excerpt: "Understand how folders become pages in the App Router.",
content:
"In the App Router, folders define URL segments and page files make those routes public. A folder like app/posts/[id] can display a different blog post depending on the id in the address bar.",
date: "2025-04-18",
},
];

export function getPostById(id) {
return posts.find((post) => post.id === Number(id));
}
13 changes: 13 additions & 0 deletions fullstack/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";

export default defineConfig([
...nextVitals,
globalIgnores([
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
7 changes: 7 additions & 0 deletions fullstack/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
13 changes: 13 additions & 0 deletions fullstack/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));

/** @type {import("next").NextConfig} */
const nextConfig = {
turbopack: {
root: __dirname,
},
};

export default nextConfig;
Loading