Compare commits

...

8 Commits
stats ... main

Author SHA1 Message Date
614adcb9f8 use UK date format
All checks were successful
Build / build (push) Successful in 3m5s
2025-04-22 13:25:28 +02:00
a5b8e40761 wording
All checks were successful
Build / build (push) Successful in 3m9s
2025-04-22 12:56:20 +02:00
e56bb8ac1c fx
Some checks failed
Build / build (push) Has been cancelled
2025-04-22 12:55:10 +02:00
d9e9824146 cleanup tags on edit
All checks were successful
Build / build (push) Successful in 3m11s
2025-04-22 12:48:23 +02:00
8b46bf354e ensure login for clipboard
All checks were successful
Build / build (push) Successful in 4m33s
2025-04-22 12:22:06 +02:00
86ec00de4c fix
All checks were successful
Build / build (push) Successful in 10s
2025-04-08 00:40:18 +02:00
49917e1eb0 deploy db
All checks were successful
Build / build (push) Successful in 16s
2025-04-08 00:36:11 +02:00
edc575b153 add clipboard
All checks were successful
Build / build (push) Successful in 3m28s
2025-04-08 00:23:36 +02:00
12 changed files with 133 additions and 10 deletions

View File

@ -16,6 +16,7 @@ RUN cp -r .next/static .next/standalone/.next/
FROM node:current-alpine AS production
COPY --from=build /app/.next/standalone /app
COPY --from=build /app/prisma /app/prisma
EXPOSE 3000
WORKDIR /app
CMD ["node", "server.js"]
CMD ["/bin/sh", "-c", "npx --yes prisma migrate deploy && node server.js"]

View File

@ -0,0 +1,7 @@
-- CreateTable
CREATE TABLE "Clipboard" (
"id" SERIAL NOT NULL,
"content" TEXT NOT NULL,
CONSTRAINT "Clipboard_pkey" PRIMARY KEY ("id")
);

View File

@ -30,3 +30,8 @@ model User {
username String @unique
password String
}
model Clipboard {
id Int @id @default(autoincrement())
content String @db.Text
}

View File

@ -114,7 +114,9 @@ export default function PostSummary({
<div>
<div>
<span>published: </span>
<span>{metadata.publishedDate.toLocaleDateString()}</span>
<span>
{metadata.publishedDate.toLocaleDateString("en-UK")}
</span>
</div>
{loggedIn && (
<div

View File

@ -100,7 +100,9 @@ export default function PostDisplay({
<div>
<div>
<span>published: </span>
<span>{post.publishedDate.toLocaleDateString()}</span>
<span>
{post.publishedDate.toLocaleDateString("en-UK")}
</span>
</div>
{loggedIn && (
<div

View File

@ -39,7 +39,22 @@ export async function savePostServer(
const slug = slugify(title, { lower: true, strict: true });
if (existingSlug) {
const post = await prisma.post.findUnique({
where: { slug: existingSlug },
include: { tags: true },
});
if (!post) {
throw new Error("Post not found");
}
await prisma.post.delete({ where: { slug: existingSlug } });
for (const tag of post.tags) {
const postsWithTag = await prisma.post.count({
where: { tags: { some: { id: tag.id } } },
});
if (postsWithTag == 0) {
await prisma.tag.delete({ where: { id: tag.id } });
}
}
}
await prisma.post.create({

View File

@ -0,0 +1,46 @@
"use client";
import { useState } from "react";
import { updateClipboard } from "./action";
export default function ClipboardComponent({
initialContent,
}: {
initialContent: string;
}) {
const [clipboardContent, setClipboardContent] = useState(initialContent);
const [typingTimeout, setTypingTimeout] = useState<NodeJS.Timeout | null>(
null
);
const contentChanged = (content: string) => {
setClipboardContent(content);
if (typingTimeout) {
clearTimeout(typingTimeout);
}
const timeout = setTimeout(async () => {
await updateClipboard(content);
}, 500);
setTypingTimeout(timeout);
};
return (
<>
<textarea
style={{
width: "100%",
resize: "none",
borderStyle: "none",
backgroundColor: "#333",
height: "20rem",
maxHeight: "80vh",
color: "#eee",
}}
onChange={(e) => contentChanged(e.target.value)}
onBlur={async (e) => {
await updateClipboard(e.target.value);
}}
value={clipboardContent}
></textarea>
</>
);
}

View File

@ -0,0 +1,19 @@
"use server";
import { PrismaClient } from "@prisma/client";
export async function updateClipboard(content: string) {
const prisma = new PrismaClient();
await prisma.clipboard.upsert({
where: { id: 1 },
update: { content },
create: { content },
});
}
export async function getClipboard(): Promise<string> {
const prisma = new PrismaClient();
const clipboard = await prisma.clipboard.findUnique({
where: { id: 1 },
});
return clipboard?.content || "";
}

View File

@ -0,0 +1,18 @@
"use server";
import { auth, signIn } from "@/auth";
import { getClipboard } from "./action";
import ClipboardComponent from "./ClipboardComponent";
export default async function ClipboardPage() {
if ((await auth())?.user == null) {
await signIn();
}
const clipboard = await getClipboard();
return (
<>
<ClipboardComponent initialContent={clipboard} />
</>
);
}

View File

@ -4,17 +4,20 @@ import Title from "@/components/Title";
import Navbar from "@/components/Navbar";
import { bodyFont } from "@/components/fonts";
import Link from "next/link";
import { auth } from "@/auth";
export const metadata: Metadata = {
title: "nrx.sh",
description: "naresh's site",
};
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const isLoggedIn = (await auth())?.user != null;
return (
<html lang="en">
<body>
@ -28,7 +31,7 @@ export default function RootLayout({
>
<Title />
<Navbar />
<Navbar isLoggedIn={isLoggedIn} />
<div
className={bodyFont.className}
style={{
@ -53,7 +56,7 @@ export default function RootLayout({
}}
className={bodyFont.className}
>
this site is built from scratch using <b>next.js</b> - it is&nbsp;
i built this site from scratch using <b>next.js</b> - it is&nbsp;
<Link
style={{ color: "#88f" }}
href={"https://git.nrx.sh/naresh/nrx.sh"}

View File

@ -4,6 +4,7 @@ import { useState } from "react";
import React from "react";
import { navBarFont } from "./fonts";
import {
ADMIN_PAGES,
PAGES,
Pages,
pathNameFromSelectedPage,
@ -11,7 +12,7 @@ import {
} from "./pages";
import { usePathname, useRouter } from "next/navigation";
export default function Navbar() {
export default function Navbar({ isLoggedIn }: { isLoggedIn: boolean }) {
const [hoveredPage, setHoveredPage] = useState<Pages | null>(null);
const pathName = usePathname();
@ -41,7 +42,10 @@ export default function Navbar() {
justifyContent: "center",
}}
>
{PAGES.map((page, index) => (
{PAGES.filter((p) => {
if (!isLoggedIn && ADMIN_PAGES.includes(p)) return false;
return true;
}).map((page, index) => (
<React.Fragment key={page}>
<div
style={navbarItem(page)}

View File

@ -1,5 +1,6 @@
export type Pages = "home" | "about" | "links" | "contact" | "blog";
export const PAGES: Pages[] = ["home", "about", "blog", "links", "contact"];
export type Pages = "home" | "about" | "links" | "contact" | "blog" | "clipboard";
export const PAGES: Pages[] = ["home", "about", "blog", "links", "contact", "clipboard"];
export const ADMIN_PAGES: Pages[] = ["clipboard"]
export function selectedPageFromPathName(pathName: string): Pages {
if (pathName === "/") {