Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .eslintrc.js

This file was deleted.

4 changes: 4 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": ["custom"]
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ yarn-error.log*
.vscode/settings.json

next-env.d.ts

# prisma generated client
src/generated/
6 changes: 5 additions & 1 deletion next.config.js → next.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
module.exports = {
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
reactStrictMode: true,
experimental: {
serverActions: {
bodySizeLimit: "10mb",
},
},
};

export default nextConfig;
2,214 changes: 1,554 additions & 660 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,41 @@
"name": "sga-puck",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepare": "husky"
"lint": "eslint src/",
"prepare": "husky",
"postinstall": "prisma generate"
},
"dependencies": {
"@prisma/client": "^6.19.0",
"@prisma/adapter-pg": "^7.5.0",
"@prisma/client": "^7.5.0",
"@puckeditor/core": "0.21.1",
"@supabase/supabase-js": "^2.99.2",
"classnames": "^2.3.2",
"lucide-react": "^0.575.0",
"next": "^15.5.13",
"next": "^16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"server-only": "^0.0.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.14",
"@types/node": "^17.0.12",
"@types/node": "^20.19.37",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"autoprefixer": "^10.4.21",
"dotenv": "^17.3.1",
"eslint-config-custom": "*",
"husky": "^9.1.7",
"postcss": "^8.5.6",
"prisma": "^6.19.0",
"prisma": "^7.5.0",
"supabase": "^2.81.3",
"tailwindcss": "^4.1.14",
"tsx": "^4.21.0",
"typescript": "^5.5.4"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}
4 changes: 2 additions & 2 deletions postcss.config.js → postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
export default {
plugins: {
"@tailwindcss/postcss": {},
autoprefixer: {},
},
}
};
13 changes: 13 additions & 0 deletions prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "tsx prisma/seed.ts",
},
datasource: {
url: process.env.DATABASE_URL,
},
});
4 changes: 2 additions & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client"
output = "../src/generated/prisma"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Document {
Expand Down
7 changes: 5 additions & 2 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Prisma, PrismaClient } from "@prisma/client";
import "dotenv/config";
import { Prisma, PrismaClient } from "../src/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import fs from "fs";
import path from "path";

const prisma = new PrismaClient();
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });

function unslugify(pathStr: string) {
return pathStr
Expand Down
34 changes: 21 additions & 13 deletions src/app/editor/MediaLibrary.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import { useRef, useState } from "react";
import { useRef, useState, useTransition } from "react";
import { File as FileIcon, Trash2, Copy } from "lucide-react";
import { uploadMediaAction, deleteMediaAction } from "../../lib/media-actions";
import type { MediaFile } from "../../lib/types";
import { useRunAction } from "./useRunAction";
import { runAction } from "./runAction";

function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
Expand Down Expand Up @@ -68,7 +68,7 @@
<div className="flex h-48 w-32 shrink-0 flex-col rounded-lg bg-gray-100 overflow-hidden">
<div className="flex h-28 items-center justify-center bg-gray-200">
{isImage ? (
<img

Check warning on line 71 in src/app/editor/MediaLibrary.tsx

View workflow job for this annotation

GitHub Actions / build

Do not use `<img>` element. Use `<Image />` from `next/image` instead. See: https://nextjs.org/docs/messages/no-img-element
src={file.url}
alt={file.name}
className="h-full w-full object-cover"
Expand Down Expand Up @@ -111,24 +111,32 @@
}

export function MediaLibrary({ files: initialFiles }: { files: MediaFile[] }) {
const [isPending, run] = useRunAction();
const [isPending, startTransition] = useTransition();
const [files, setFiles] = useState(initialFiles);

async function handleUpload(file: File) {
function handleUpload(file: File) {
const formData = new FormData();
formData.append("file", file);
const result = await run(uploadMediaAction(formData));
if (result.success) {
setFiles((prev) => [...prev, result.data]);
}
startTransition(async () => {
const result = await runAction(uploadMediaAction(formData));
if (result.success) {
setFiles((prev) => [...prev, result.data]);
} else {
alert(result.error);
}
});
}

async function handleDelete(file: MediaFile) {
function handleDelete(file: MediaFile) {
if (!window.confirm(`Delete "${file.name}"?`)) return;
const result = await run(deleteMediaAction({ name: file.name }));
if (result.success) {
setFiles((prev) => prev.filter((f) => f.name !== file.name));
}
startTransition(async () => {
const result = await runAction(deleteMediaAction({ name: file.name }));
if (result.success) {
setFiles((prev) => prev.filter((f) => f.name !== file.name));
} else {
alert(result.error);
}
});
}

return (
Expand Down
52 changes: 32 additions & 20 deletions src/app/editor/RouteTable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import Link from "next/link";
import { useRef, useState } from "react";
import { useRef, useState, useTransition } from "react";
import { Check, SquarePen, Trash2, X } from "lucide-react";
import {
createRouteAction,
updateRouteAction,
deleteRouteAction,
} from "../../lib/actions";
import { useRunAction } from "./useRunAction";
import { runAction } from "./runAction";

type RouteRow = {
id: number;
Expand Down Expand Up @@ -181,33 +181,45 @@ export function RouteTable({
routes: RouteRow[];
documents: DocumentOption[];
}) {
const [isPending, run] = useRunAction();
const [isPending, startTransition] = useTransition();
const [creating, setCreating] = useState(false);
const [routes, setRoutes] = useState(initialRoutes);

async function handleCreate(path: string, documentId: number) {
function handleCreate(path: string, documentId: number) {
setCreating(false);
const result = await run(createRouteAction({ path, documentId }));
if (result.success) {
const docName = documents.find((d) => d.id === documentId)?.name ?? "";
setRoutes((prev) => [...prev, { id: result.data.routeId, path, documentId, documentName: docName }]);
}
startTransition(async () => {
const result = await runAction(createRouteAction({ path, documentId }));
if (result.success) {
const docName = documents.find((d) => d.id === documentId)?.name ?? "";
setRoutes((prev) => [...prev, { id: result.data.routeId, path, documentId, documentName: docName }]);
} else {
alert(result.error);
}
});
}

async function handleUpdate(id: number, path: string, documentId: number) {
const result = await run(updateRouteAction({ id, path, documentId }));
if (result.success) {
const docName = documents.find((d) => d.id === documentId)?.name ?? "";
setRoutes((prev) => prev.map((r) => (r.id === id ? { ...r, path, documentId, documentName: docName } : r)));
}
function handleUpdate(id: number, path: string, documentId: number) {
startTransition(async () => {
const result = await runAction(updateRouteAction({ id, path, documentId }));
if (result.success) {
const docName = documents.find((d) => d.id === documentId)?.name ?? "";
setRoutes((prev) => prev.map((r) => (r.id === id ? { ...r, path, documentId, documentName: docName } : r)));
} else {
alert(result.error);
}
});
}

async function handleDelete(route: RouteRow) {
function handleDelete(route: RouteRow) {
if (!window.confirm(`Delete route "${route.path}"?`)) return;
const result = await run(deleteRouteAction({ id: route.id }));
if (result.success) {
setRoutes((prev) => prev.filter((r) => r.id !== route.id));
}
startTransition(async () => {
const result = await runAction(deleteRouteAction({ id: route.id }));
if (result.success) {
setRoutes((prev) => prev.filter((r) => r.id !== route.id));
} else {
alert(result.error);
}
});
}

return (
Expand Down
26 changes: 13 additions & 13 deletions src/app/editor/[id]/SaveButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { createUsePuck } from "@puckeditor/core";
import { useState } from "react";
import { useTransition } from "react";
import { useDocumentContext } from "./client";
import { saveVersionAction } from "../../../lib/actions";
import { runAction } from "../runAction";

const usePuck = createUsePuck();

Expand All @@ -12,21 +13,20 @@ export function SaveButton() {
const dispatch = usePuck((s) => s.dispatch);
const { documentId, addVersion } = useDocumentContext();

const [isSaving, setIsSaving] = useState(false);
const [isSaving, startTransition] = useTransition();

const handleSave = async () => {
setIsSaving(true);
const result = await saveVersionAction({ documentId, content: data });
const handleSave = () => {
startTransition(async () => {
const result = await runAction(saveVersionAction({ documentId, content: data }));

if (result.success === false) {
alert(`Error: ${result.error}`);
setIsSaving(false);
return;
}
if (result.success === false) {
alert(result.error);
return;
}

dispatch({ type: "setData", data });
addVersion(result.data.version);
setIsSaving(false);
dispatch({ type: "setData", data });
addVersion(result.data.version);
});
};

return (
Expand Down
24 changes: 12 additions & 12 deletions src/app/editor/[id]/VersionPluginContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
"use client";

import { useState } from "react";
import { useTransition } from "react";
import { useRouter } from "next/navigation";
import { useDocumentContext } from "./client";
import { publishVersionAction } from "../../../lib/actions";
import { runAction } from "../runAction";
import { VersionListPanel } from "./VersionListPanel";

export function VersionPluginContainer() {
const router = useRouter();
const { documentId, versionId, publishedVersionId, versions, setPublishedVersionId } = useDocumentContext();
const [isPublishing, setIsPublishing] = useState(false);
const [isPublishing, startTransition] = useTransition();

const handlePublishVersion = async (targetVersionId: number) => {
setIsPublishing(true);
const result = await publishVersionAction({ documentId, versionId: targetVersionId });
const handlePublishVersion = (targetVersionId: number) => {
startTransition(async () => {
const result = await runAction(publishVersionAction({ documentId, versionId: targetVersionId }));

if (result.success === false) {
alert(`Error: ${result.error}`);
setIsPublishing(false);
return;
}
if (result.success === false) {
alert(result.error);
return;
}

setPublishedVersionId(targetVersionId);
setIsPublishing(false);
setPublishedVersionId(targetVersionId);
});
};

const handleLoadVersion = (versionIdToLoad: number) => {
Expand Down
9 changes: 9 additions & 0 deletions src/app/editor/runAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ActionResult } from "../../lib/types";

export async function runAction<T>(action: Promise<ActionResult<T>>): Promise<ActionResult<T>> {
try {
return await action;
} catch {
return { success: false, error: "Something went wrong. Please try again." };
}
}
24 changes: 0 additions & 24 deletions src/app/editor/useRunAction.ts

This file was deleted.

Binary file removed src/app/favicon.ico
Binary file not shown.
Loading
Loading