From 5f4f8ed14440610bf8001a97d95d350837599fd4 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 15 Jan 2025 02:09:43 -0500 Subject: [PATCH] rework entire project with nextjs --- .gitignore | 158 +- Dockerfile | 9 - README.md | 5 +- app.js | 173 - app/admin/page.tsx | 79 + app/admin/post/page.tsx | 107 + app/admin/users/new/page.tsx | 67 + app/admin/users/page.tsx | 38 + app/categories/page.tsx | 82 + app/globals.css | 59 + app/layout.tsx | 35 + app/page.tsx | 76 + bun.lockb | Bin 0 -> 250365 bytes components.json | 21 + components/Navbar.tsx | 141 + components/Sidebar.tsx | 99 + components/ui/badge.tsx | 36 + components/ui/button.tsx | 57 + components/ui/card.tsx | 76 + components/ui/dropdown-menu.tsx | 201 ++ components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/select.tsx | 159 + components/ui/sheet.tsx | 140 + components/ui/textarea.tsx | 22 + config.json | 5 + docker-compose.yml | 18 - eslint.config.mjs | 16 + lib/utils.ts | 6 + middleware/auth.js | 33 - models/admin.js | 18 - models/post.js | 9 - next.config.ts | 9 + package-lock.json | 5895 +++++++++++++++++++++++++++++++ package.json | 41 + postcss.config.mjs | 8 + strings.json | 57 + tailwind.config.ts | 62 + tsconfig.json | 27 + views/admin/dashboard.ejs | 68 - views/admin/edit.ejs | 42 - views/admin/login.ejs | 56 - views/admin/new.ejs | 48 - views/admin/setup.ejs | 60 - views/index.ejs | 59 - views/new.ejs | 39 - 46 files changed, 7705 insertions(+), 759 deletions(-) delete mode 100644 Dockerfile delete mode 100644 app.js create mode 100644 app/admin/page.tsx create mode 100644 app/admin/post/page.tsx create mode 100644 app/admin/users/new/page.tsx create mode 100644 app/admin/users/page.tsx create mode 100644 app/categories/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100755 bun.lockb create mode 100644 components.json create mode 100644 components/Navbar.tsx create mode 100644 components/Sidebar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 config.json delete mode 100644 docker-compose.yml create mode 100644 eslint.config.mjs create mode 100644 lib/utils.ts delete mode 100644 middleware/auth.js delete mode 100644 models/admin.js delete mode 100644 models/post.js create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 strings.json create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json delete mode 100644 views/admin/dashboard.ejs delete mode 100644 views/admin/edit.ejs delete mode 100644 views/admin/login.ejs delete mode 100644 views/admin/new.ejs delete mode 100644 views/admin/setup.ejs delete mode 100644 views/index.ejs delete mode 100644 views/new.ejs diff --git a/.gitignore b/.gitignore index ff3c7bd..5ef6a52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,133 +1,41 @@ -# Logs -logs -*.log +# 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* -lerna-debug.log* .pnpm-debug.log* -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +# env files (can opt-in for committing if needed) +.env* -# Runtime data -pids -*.pid -*.seed -*.pid.lock +# vercel +.vercel -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache +# typescript *.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -# idea -.idea \ No newline at end of file +next-env.d.ts diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9a6bdba..0000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM node:20-slim - -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . - -EXPOSE 3000 -CMD ["node", "app.js"] \ No newline at end of file diff --git a/README.md b/README.md index 7deba1b..4d42947 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # blogpop -[![Last Update](https://img.shields.io/badge/last_update-17_Nov_2024-blue)](#) +[![Last Update](https://img.shields.io/badge/last_update-15_Jan_2025-blue)](#) + +A simple blogging platform built with Next.js, shadcn/ui and Tailwind CSS. -A simple blogging platform built with Node.js, Express, and MongoDB. diff --git a/app.js b/app.js deleted file mode 100644 index fb7fb7c..0000000 --- a/app.js +++ /dev/null @@ -1,173 +0,0 @@ -const express = require('express'); -const mongoose = require('mongoose'); -const bodyParser = require('body-parser'); -const { marked } = require('marked'); -const session = require('express-session'); -const MongoStore = require('connect-mongo'); -const bcrypt = require('bcrypt'); -const Admin = require('./models/admin'); -const Post = require('./models/post'); -const { requireAuth, checkSetup } = require('./middleware/auth'); - -const app = express(); - -mongoose.connect('mongodb://mongodb:27017/blog', { - useNewUrlParser: true, - useUnifiedTopology: true -}); - -app.use(session({ - secret: process.env.SESSION_SECRET || 'your-rewkufhriufhweirhfuiewrhfiuewrhfiuehrwifuerhwuifwher-key', - resave: false, - saveUninitialized: true, - store: MongoStore.create({ - mongoUrl: 'mongodb://mongodb:27017/blog', - ttl: 24 * 60 * 60 - }), - cookie: { - secure: process.env.NODE_ENV === 'production', - maxAge: 1000 * 60 * 60 * 24 - } -})); - -marked.setOptions({ - breaks: true, - gfm: true -}); - -app.set('view engine', 'ejs'); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(express.static('public')); -app.locals.marked = marked; - -app.use((req, res, next) => { - console.log('Session data after middleware:', req.session); - res.locals.isAuthenticated = req.session.isAuthenticated || false; - next(); -}); - -app.get('/', async (req, res) => { - const posts = await Post.find().sort({ createdAt: -1 }); - console.log('hi'); - res.render('index', { posts }); -}); - -app.get('/admin/setup', checkSetup, (req, res) => { - console.log('trying'); - res.render('admin/setup'); -}); - -app.post('/admin/setup', checkSetup, async (req, res) => { - try { - const admin = new Admin({ - username: req.body.username, - password: req.body.password, - email: req.body.email, - setupComplete: true - }); - await admin.save(); - req.session.isAuthenticated = true; - res.redirect('/admin/dashboard'); - } catch (error) { - console.error('Setup failed:', error); - res.render('admin/setup', { error: 'Setup failed. Please try again.' }); - } -}); - -app.post('/admin/login', async (req, res) => { - try { - const admin = await Admin.findOne({ username: req.body.username }); - if (admin && await bcrypt.compare(req.body.password, admin.password)) { - req.session.isAuthenticated = true; - req.session.save((err) => { - if (err) { - console.error('Session save error:', err); - return res.render('admin/login', { error: 'Login failed' }); - } - console.log('Session data after login:', req.session); - console.log('Login successful, redirecting to dashboard'); - return res.redirect('/admin/dashboard'); - }); - } else { - console.log('Invalid credentials'); - res.render('admin/login', { error: 'Invalid credentials' }); - } - } catch (error) { - console.error('Login failed:', error); - res.render('admin/login', { error: 'Login failed' }); - } -}); - -app.get('/admin/login', (req, res) => { - res.render('admin/login'); -}); - -app.post('/admin/login', async (req, res) => { - try { - const admin = await Admin.findOne({ username: req.body.username }); - if (admin && await bcrypt.compare(req.body.password, admin.password)) { - req.session.isAuthenticated = true; - console.log("in loop"); - return res.redirect('/admin/dashboard'); - } - res.render('admin/login', { error: 'Invalid credentials' }); - } catch (error) { - console.log('Login failed:', error); - res.render('admin/login', { error: 'Login failed' }); - } -}); - -app.get('/admin/logout', (req, res) => { - req.session.destroy((err) => { - if (err) { - console.error('Logout failed:', err); - } - res.redirect('/'); - }); -}); - -app.get('/admin/dashboard', requireAuth, async (req, res) => { - try { - const posts = await Post.find().sort({ createdAt: -1 }); - res.render('admin/dashboard', { posts }); - } catch (error) { - console.error('Dashboard error:', error); - res.status(500).send('Error loading dashboard'); - } -}); - -app.get('/admin/new', requireAuth, (req, res) => { - res.render('admin/new'); -}); - -app.post('/admin/posts', requireAuth, async (req, res) => { - const post = new Post({ - title: req.body.title, - content: req.body.content - }); - await post.save(); - res.redirect('/admin/dashboard'); -}); - -app.get('/admin/posts/:id/edit', requireAuth, async (req, res) => { - const post = await Post.findById(req.params.id); - res.render('admin/edit', { post }); -}); - -app.post('/admin/posts/:id', requireAuth, async (req, res) => { - await Post.findByIdAndUpdate(req.params.id, { - title: req.body.title, - content: req.body.content - }); - res.redirect('/admin/dashboard'); -}); - -app.post('/admin/posts/:id/delete', requireAuth, async (req, res) => { - await Post.findByIdAndDelete(req.params.id); - res.redirect('/admin/dashboard'); -}); - -const PORT = 2389; -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); \ No newline at end of file diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..f181a91 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,79 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import strings from "@/strings.json" +import { PlusCircle, UserPlus } from "lucide-react" + +const posts = [ + { + id: 1, + title: "Sample Post 1", + description: "Description", + date: "2025-01-14", + category: "Example Category 1", + slug: "sample-post-1", + }, + { + id: 2, + title: "Sample Post 2", + description: "Description", + date: "2025-01-14", + category: "Example Category 1", + slug: "sample-post-2", + }, + { + id: 3, + title: "Sample Post 3", + description: "Description", + date: "2025-01-14", + category: "Example Category 2", + slug: "sample-post-3", + }, + { + id: 4, + title: "Sample Post 4", + description: "Description", + date: "2025-01-14", + category: "Example Category 2", + slug: "sample-post-4", + }, +] + +export default function Admin() { + return ( +
+

{strings.adminHeader}

+
+ + +
+ {strings.totalPostsCardTitle} + + {posts.length} + +
+
+
+ + + Quick Actions + + + + + + +
+
+ ) +} + diff --git a/app/admin/post/page.tsx b/app/admin/post/page.tsx new file mode 100644 index 0000000..57a19b1 --- /dev/null +++ b/app/admin/post/page.tsx @@ -0,0 +1,107 @@ +"use client" + +import { useState } from "react" +import dynamic from "next/dynamic" +import strings from "@/strings.json" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" + +const MDEditor = dynamic(() => import("@uiw/react-md-editor"), { + ssr: false +}) + +import "@uiw/react-md-editor/markdown-editor.css" +import "@uiw/react-markdown-preview/markdown.css" + +const categories = ["Example Category 1", "Example Category 2"] + +export default function CreatePost() { + const [title, setTitle] = useState("") + const [description, setDescription] = useState("") + const [category, setCategory] = useState("") + const [slug, setSlug] = useState("") + const [content, setContent] = useState("") + + const handleEditorChange = (value: string | undefined) => { + setContent(value || "") + } + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + // TODO: handle form submission here! + console.log({ title, description, category, slug, content }) + } + + return ( +
+

{strings.adminNewPostHeader}

+ +
+ + {strings.newPostCardTitle} + + +
+ + setTitle(e.target.value)} + placeholder={strings.newPostTitleFieldPlaceholder} + /> +
+
+ + -
- - Cancel -
- - - \ No newline at end of file diff --git a/views/admin/login.ejs b/views/admin/login.ejs deleted file mode 100644 index 3ecb76a..0000000 --- a/views/admin/login.ejs +++ /dev/null @@ -1,56 +0,0 @@ - - - - Admin Login - - - -

Admin Login

-<% if (typeof error !== 'undefined') { %> -
<%= error %>
-<% } %> -
-
- - -
-
- - -
- -
- - \ No newline at end of file diff --git a/views/admin/new.ejs b/views/admin/new.ejs deleted file mode 100644 index e487074..0000000 --- a/views/admin/new.ejs +++ /dev/null @@ -1,48 +0,0 @@ - - - - New Post - - - -

New Blog Post

-
- - -
- - Cancel -
-
- - \ No newline at end of file diff --git a/views/admin/setup.ejs b/views/admin/setup.ejs deleted file mode 100644 index fe9b995..0000000 --- a/views/admin/setup.ejs +++ /dev/null @@ -1,60 +0,0 @@ - - - - Blog Setup - - - -

Initial Setup

-<% if (typeof error !== 'undefined') { %> -
<%= error %>
-<% } %> -
-
- - -
-
- - -
-
- - -
- -
- - \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs deleted file mode 100644 index cbf2c5d..0000000 --- a/views/index.ejs +++ /dev/null @@ -1,59 +0,0 @@ - - - - My Blog - - - -
-

My Blog

-

Powered by free and open source software.

-
- <% if (isAuthenticated) { %> - Dashboard - Logout - <% } else { %> - Login - <% } %> -
-
-<% posts.forEach(function(post){ %> -
-

<%= post.title %>

-
<%= post.createdAt.toLocaleDateString() %>
- <%- marked(post.content) %> -
-<% }); %> - - \ No newline at end of file diff --git a/views/new.ejs b/views/new.ejs deleted file mode 100644 index 8b7bbe3..0000000 --- a/views/new.ejs +++ /dev/null @@ -1,39 +0,0 @@ - - - - New Post - - - -

New Blog Post

-
- - - -
- - \ No newline at end of file