initial commit
This commit is contained in:
42
src/app/(front)/layout.tsx
Normal file
42
src/app/(front)/layout.tsx
Normal 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
102
src/app/(front)/page.tsx
Normal 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">
|
||||
© {new Date().getFullYear()} KantanCMS. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
108
src/app/(front)/posts/[id]/page.tsx
Normal file
108
src/app/(front)/posts/[id]/page.tsx
Normal 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">
|
||||
© {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">
|
||||
© {new Date().getFullYear()} KantanCMS. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
src/app/(front)/profile/page.tsx
Normal file
38
src/app/(front)/profile/page.tsx
Normal 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">
|
||||
© {new Date().getFullYear()} KantanCMS. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user