initial commit

This commit is contained in:
Ken Yasue
2025-03-25 06:19:44 +01:00
parent b97fa96c25
commit 9aef2ad891
71 changed files with 13016 additions and 1 deletions

View File

@ -0,0 +1,42 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "../globals.css";
import DatabaseInitializer from "@/lib/components/DatabaseInitializer";
import { ThemeProvider } from "@/lib/context/ThemeContext";
import ThemeToggleWrapper from "@/lib/components/ThemeToggleWrapper";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "KantanCMS",
description: "A simple CMS with admin console and frontpage",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{/* Initialize database connection */}
<DatabaseInitializer />
<ThemeProvider>
<ThemeToggleWrapper />
{children}
</ThemeProvider>
</body>
</html>
);
}

102
src/app/(front)/page.tsx Normal file
View File

@ -0,0 +1,102 @@
import Link from 'next/link';
import { Post, getDataSource } from '@/lib/database';
import FrontendHeader from '@/lib/components/FrontendHeader';
import PostSidebar from '@/lib/components/PostSidebar';
import EditorJSRenderer from '@/lib/components/EditorJSRenderer';
export default async function Home() {
// Fetch posts from the database
const dataSource = await getDataSource();
const postRepository = dataSource.getRepository(Post);
const posts = await postRepository.find({
relations: ['user'],
order: { createdAt: 'DESC' },
take: 10 // Limit to 10 most recent posts
});
return (
<div className="min-h-screen bg-gray-50">
<FrontendHeader />
<main>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="flex flex-col md:flex-row gap-6">
{/* Sidebar */}
<div className="md:w-1/4">
<PostSidebar />
</div>
{/* Main content */}
<div className="md:w-3/4 border-4 border-dashed border-gray-200 rounded-lg p-4 min-h-96">
<h2 className="text-2xl font-bold text-black mb-6">Latest Posts</h2>
{posts.length > 0 ? (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<div key={post.id} className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg font-medium text-gray-900 truncate">
{post.title}
</h3>
<div className="mt-2 text-sm text-gray-500 line-clamp-3 overflow-hidden max-h-16">
{/* Try to extract a preview from the EditorJS content */}
{(() => {
try {
const content = JSON.parse(post.content);
if (content.blocks && content.blocks.length > 0) {
// Get the first block's text content
const firstBlock = content.blocks[0];
if (firstBlock.type === 'paragraph' && firstBlock.data.text) {
return firstBlock.data.text.substring(0, 150) + (firstBlock.data.text.length > 150 ? '...' : '');
}
}
} catch (e) {
// If parsing fails, just show the first 150 characters
}
return post.content.substring(0, 150) + (post.content.length > 150 ? '...' : '');
})()}
</div>
<div className="mt-3">
<Link
href={`/posts/${post.id}`}
className="text-indigo-600 hover:text-indigo-900 text-sm font-medium"
>
Read more
</Link>
</div>
</div>
<div className="bg-gray-50 px-4 py-4 sm:px-6">
<div className="text-sm text-gray-500">
Posted on {new Date(post.createdAt).toLocaleDateString()}
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12">
<p className="text-gray-500 mb-4">No posts found.</p>
<p className="text-gray-500">
Visit the{' '}
<Link href="/admin" className="text-indigo-600 hover:text-indigo-900">
admin panel
</Link>{' '}
to create your first post.
</p>
</div>
)}
</div>
</div>
</div>
</div>
</main>
<footer className="bg-white">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<p className="text-center text-gray-500 text-sm">
&copy; {new Date().getFullYear()} KantanCMS. All rights reserved.
</p>
</div>
</footer>
</div>
);
}

View File

@ -0,0 +1,108 @@
import Link from 'next/link';
import { Post, getDataSource } from '@/lib/database';
import FrontendHeader from '@/lib/components/FrontendHeader';
import PostSidebar from '@/lib/components/PostSidebar';
import EditorJSRenderer from '@/lib/components/EditorJSRenderer';
interface PostDetailProps {
params: {
id: string;
};
}
// Generate static params for all posts
export async function generateStaticParams() {
const dataSource = await getDataSource();
const postRepository = dataSource.getRepository(Post);
const posts = await postRepository.find();
return posts.map((post) => ({
id: post.id,
}));
}
export default async function PostDetail({ params }: PostDetailProps) {
// Fetch the post from the database
const dataSource = await getDataSource();
const postRepository = dataSource.getRepository(Post);
const post = await postRepository.findOne({
where: { id: params.id },
relations: ['user', 'parent']
});
if (!post) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col">
<FrontendHeader />
<main className="flex-grow">
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="text-center py-12">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Post Not Found</h2>
<p className="text-gray-500 mb-4">The post you are looking for does not exist.</p>
<Link href="/" className="text-indigo-600 hover:text-indigo-900">
Return to homepage
</Link>
</div>
</div>
</div>
</main>
<footer className="bg-white">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<p className="text-center text-gray-500 text-sm">
&copy; {new Date().getFullYear()} KantanCMS. All rights reserved.
</p>
</div>
</footer>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 flex flex-col">
<FrontendHeader />
<main className="flex-grow">
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="flex flex-col md:flex-row gap-6">
{/* Sidebar */}
<div className="md:w-1/4">
<PostSidebar />
</div>
{/* Main content */}
<div className="md:w-3/4">
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h2 className="text-2xl font-bold text-gray-900">{post.title}</h2>
<p className="mt-1 max-w-2xl text-sm text-gray-500">
Posted on {new Date(post.createdAt).toLocaleDateString()}
{post.user && ` by ${post.user.username}`}
</p>
</div>
<div className="border-t border-gray-200">
<div className="px-4 py-5 sm:p-6">
<EditorJSRenderer data={post.content} />
</div>
</div>
<div className="border-t border-gray-200 px-4 py-4 sm:px-6">
<Link href="/" className="text-indigo-600 hover:text-indigo-900">
Back to all posts
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer className="bg-white mt-auto">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<p className="text-center text-gray-500 text-sm">
&copy; {new Date().getFullYear()} KantanCMS. All rights reserved.
</p>
</div>
</footer>
</div>
);
}

View File

@ -0,0 +1,38 @@
'use client';
import ProfileEditor from '@/lib/components/ProfileEditor';
import Link from 'next/link';
export default function FrontendProfilePage() {
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8 flex justify-between items-center">
<h1 className="text-3xl font-bold text-gray-900">KantanCMS</h1>
<Link
href="/"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
>
Back to Home
</Link>
</div>
</header>
<main>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="max-w-2xl mx-auto">
<ProfileEditor isAdmin={false} />
</div>
</div>
</div>
</main>
<footer className="bg-white">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<p className="text-center text-gray-500 text-sm">
&copy; {new Date().getFullYear()} KantanCMS. All rights reserved.
</p>
</div>
</footer>
</div>
);
}