From 6c7dfd8b402b6d35cb1acee16cea4110d6646317 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 18 Feb 2026 09:32:47 -0600 Subject: [PATCH] Add working blog backup functionality - Replaced default Next.js template with daily message backup UI - Added API endpoint for CRUD operations on messages - Created message form with date picker and textarea - Grouped messages by date with expandable sections - Data persists to data/messages.json --- data/messages.json | 1 + next.config.ts | 2 +- src/app/api/messages/route.ts | 72 +++++++++++ src/app/page.tsx | 217 +++++++++++++++++++++++++--------- 4 files changed, 235 insertions(+), 57 deletions(-) create mode 100644 data/messages.json create mode 100644 src/app/api/messages/route.ts diff --git a/data/messages.json b/data/messages.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/data/messages.json @@ -0,0 +1 @@ +[] diff --git a/next.config.ts b/next.config.ts index e9ffa30..225e495 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: 'standalone', }; export default nextConfig; diff --git a/src/app/api/messages/route.ts b/src/app/api/messages/route.ts new file mode 100644 index 0000000..ba25234 --- /dev/null +++ b/src/app/api/messages/route.ts @@ -0,0 +1,72 @@ +import { NextResponse } from "next/server"; +import { readFileSync, writeFileSync, existsSync } from "fs"; +import { join } from "path"; + +const DATA_FILE = join(process.cwd(), "data", "messages.json"); + +interface Message { + id: string; + date: string; + content: string; + timestamp: number; +} + +function getMessages(): Message[] { + if (!existsSync(DATA_FILE)) { + return []; + } + try { + const data = readFileSync(DATA_FILE, "utf-8"); + return JSON.parse(data); + } catch { + return []; + } +} + +function saveMessages(messages: Message[]) { + const dir = join(process.cwd(), "data"); + if (!existsSync(dir)) { + require("fs").mkdirSync(dir, { recursive: true }); + } + writeFileSync(DATA_FILE, JSON.stringify(messages, null, 2)); +} + +export async function GET() { + const messages = getMessages(); + return NextResponse.json(messages); +} + +export async function POST(request: Request) { + const { content, date } = await request.json(); + + if (!content || !date) { + return NextResponse.json({ error: "Content and date required" }, { status: 400 }); + } + + const messages = getMessages(); + const newMessage: Message = { + id: Date.now().toString(), + date, + content, + timestamp: Date.now(), + }; + + messages.unshift(newMessage); + saveMessages(messages); + + return NextResponse.json(newMessage); +} + +export async function DELETE(request: Request) { + const { id } = await request.json(); + + if (!id) { + return NextResponse.json({ error: "ID required" }, { status: 400 }); + } + + let messages = getMessages(); + messages = messages.filter((m) => m.id !== id); + saveMessages(messages); + + return NextResponse.json({ success: true }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 295f8fd..237a55c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,65 +1,170 @@ -import Image from "next/image"; +"use client"; + +import { useState, useEffect } from "react"; + +interface Message { + id: string; + date: string; + content: string; + timestamp: number; +} export default function Home() { + const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(""); + const [selectedDate, setSelectedDate] = useState(() => { + const today = new Date(); + return today.toISOString().split("T")[0]; + }); + const [loading, setLoading] = useState(false); + + useEffect(() => { + fetchMessages(); + }, []); + + async function fetchMessages() { + try { + const res = await fetch("/api/messages"); + const data = await res.json(); + setMessages(data); + } catch (err) { + console.error("Failed to fetch messages:", err); + } + } + + async function addMessage(e: React.FormEvent) { + e.preventDefault(); + if (!newMessage.trim()) return; + + setLoading(true); + try { + const res = await fetch("/api/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content: newMessage, date: selectedDate }), + }); + + if (res.ok) { + setNewMessage(""); + fetchMessages(); + } + } catch (err) { + console.error("Failed to add message:", err); + } finally { + setLoading(false); + } + } + + async function deleteMessage(id: string) { + if (!confirm("Delete this message?")) return; + + try { + const res = await fetch("/api/messages", { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id }), + }); + + if (res.ok) { + fetchMessages(); + } + } catch (err) { + console.error("Failed to delete message:", err); + } + } + + // Group messages by date + const groupedMessages = messages.reduce((acc, msg) => { + if (!acc[msg.date]) acc[msg.date] = []; + acc[msg.date].push(msg); + return acc; + }, {} as Record); + + const sortedDates = Object.keys(groupedMessages).sort((a, b) => + new Date(b).getTime() - new Date(a).getTime() + ); + return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark +
+
+

📓 Daily Blog Backup

+

Backup of your daily messages and thoughts

+
+ + {/* Add new message */} +
+
+ + setSelectedDate(e.target.value)} + className="w-full bg-zinc-800 border border-zinc-700 rounded px-3 py-2 text-zinc-100 focus:outline-none focus:border-blue-500" /> - Deploy Now - - +
+ +