diff --git a/next.config.ts b/next.config.ts index 68a6c64..04361fe 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,18 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "**", + }, + { + protocol: "http", + hostname: "**", + }, + ], + }, }; export default nextConfig; diff --git a/src/app/admin/editor/[id]/page.tsx b/src/app/admin/editor/[id]/page.tsx index bcaebbc..7c70818 100644 --- a/src/app/admin/editor/[id]/page.tsx +++ b/src/app/admin/editor/[id]/page.tsx @@ -8,6 +8,7 @@ import { Skeleton } from "@/components/ui/Skeleton"; import { MarkdownEditor } from "@/components/editor/MarkdownEditor"; import { ImageUpload } from "@/components/editor/ImageUpload"; import { PostContent } from "@/components/post/PostContent"; +import { SaveStatus } from "@/components/SaveStatus"; import { api } from "@/lib/api"; interface EditPostPageProps { @@ -22,7 +23,7 @@ export default function EditPostEditor({ params }: EditPostPageProps) { const [published, setPublished] = useState(false); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); - const [saved, setSaved] = useState(false); + const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle"); useEffect(() => { async function fetchPost() { @@ -45,15 +46,16 @@ export default function EditPostEditor({ params }: EditPostPageProps) { if (!title.trim()) return; setSaving(true); + setSaveStatus("saving"); try { await api.updatePost(id, { title, content, published, }); - setSaved(true); + setSaveStatus("saved"); } catch { - alert("Failed to save post"); + setSaveStatus("error"); } finally { setSaving(false); } @@ -65,11 +67,11 @@ export default function EditPostEditor({ params }: EditPostPageProps) { }; useEffect(() => { - if (saved) { - const timer = setTimeout(() => setSaved(false), 3000); + if (saveStatus === "saved" || saveStatus === "error") { + const timer = setTimeout(() => setSaveStatus("idle"), 3000); return () => clearTimeout(timer); } - }, [saved]); + }, [saveStatus]); if (loading) { return ( @@ -92,6 +94,8 @@ export default function EditPostEditor({ params }: EditPostPageProps) { />
+ +
@@ -112,7 +116,7 @@ export default function EditPostEditor({ params }: EditPostPageProps) { -
+
diff --git a/src/app/admin/editor/page.tsx b/src/app/admin/editor/page.tsx index 1959bcb..8d47057 100644 --- a/src/app/admin/editor/page.tsx +++ b/src/app/admin/editor/page.tsx @@ -7,6 +7,7 @@ import { Button } from "@/components/ui/Button"; import { MarkdownEditor } from "@/components/editor/MarkdownEditor"; import { ImageUpload } from "@/components/editor/ImageUpload"; import { PostContent } from "@/components/post/PostContent"; +import { SaveStatus } from "@/components/SaveStatus"; import { api } from "@/lib/api"; export default function NewPostEditor() { @@ -15,24 +16,25 @@ export default function NewPostEditor() { const [content, setContent] = useState(""); const [published, setPublished] = useState(false); const [saving, setSaving] = useState(false); - const [saved, setSaved] = useState(false); + const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle"); const handleSave = useCallback(async () => { if (!title.trim()) return; setSaving(true); + setSaveStatus("saving"); try { const post = await api.createPost({ title, content, published, }); - setSaved(true); + setSaveStatus("saved"); setTimeout(() => { router.push(`/admin/editor/${post.id}`); }, 1000); } catch { - alert("Failed to save post"); + setSaveStatus("error"); } finally { setSaving(false); } @@ -44,11 +46,11 @@ export default function NewPostEditor() { }; useEffect(() => { - if (saved) { - const timer = setTimeout(() => setSaved(false), 3000); + if (saveStatus === "saved" || saveStatus === "error") { + const timer = setTimeout(() => setSaveStatus("idle"), 3000); return () => clearTimeout(timer); } - }, [saved]); + }, [saveStatus]); return ( @@ -62,6 +64,8 @@ export default function NewPostEditor() { />
+ +
@@ -82,7 +86,7 @@ export default function NewPostEditor() {
-
+
diff --git a/src/app/drafts/page.tsx b/src/app/drafts/page.tsx index 6dc3061..adaa8e1 100644 --- a/src/app/drafts/page.tsx +++ b/src/app/drafts/page.tsx @@ -95,7 +95,6 @@ function DraftsContent() { size="sm" onClick={() => handlePublish(post.id)} disabled={publishing === post.id} - className="border-green-600 text-green-600 hover:bg-green-600 hover:text-white" > {publishing === post.id ? "Publishing..." : "Publish"} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 27d95dd..aa16d7f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -31,7 +31,7 @@ export default function RootLayout({ return (
diff --git a/src/app/page.tsx b/src/app/page.tsx index 9e7b476..1322ad1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -43,7 +43,7 @@ export default function HomePage() { {error && ( -

{error}

+

{error}

)} {loading && ( diff --git a/src/app/posts/[id]/edit/page.tsx b/src/app/posts/[id]/edit/page.tsx index 477f213..4a62b24 100644 --- a/src/app/posts/[id]/edit/page.tsx +++ b/src/app/posts/[id]/edit/page.tsx @@ -17,7 +17,7 @@ interface EditPostPageProps { params: Promise<{ id: string }>; } -const AUTOSAVE_DELAY = 300; +const AUTOSAVE_DELAY = 2000; function EditPostContent({ id }: { id: string }) { const router = useRouter(); @@ -173,7 +173,7 @@ function EditPostContent({ id }: { id: string }) {
-
+
diff --git a/src/app/posts/[id]/page.tsx b/src/app/posts/[id]/page.tsx index d6b069d..560e6d8 100644 --- a/src/app/posts/[id]/page.tsx +++ b/src/app/posts/[id]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect, use } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, notFound } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; import { Container } from "@/components/layout/Container"; @@ -49,8 +49,7 @@ export default function PostPage({ params }: PostPageProps) { }; if (error) { - router.push("/not-found"); - return null; + notFound(); } return ( diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 7adb29f..111c13a 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -93,14 +93,14 @@ export default function RegisterPage() { ? "border-red-500 animate-shake" : password.length > 0 ? passwordValid - ? "border-green-500" - : "border-yellow-500" + ? "border-black" + : "border-neutral-400" : "border-neutral-200 focus:border-black" }`} /> {password.length}/{MIN_PASSWORD_LENGTH} diff --git a/src/components/editor/ImageUpload.tsx b/src/components/editor/ImageUpload.tsx index 654c7f6..5c637a2 100644 --- a/src/components/editor/ImageUpload.tsx +++ b/src/components/editor/ImageUpload.tsx @@ -87,10 +87,10 @@ export function ImageUpload({ onUpload }: ImageUploadProps) { onDragLeave={handleDragLeave} onDrop={handleDrop} className={clsx( - "border-2 border-dashed p-8 text-center cursor-pointer transition-colors", + "border p-8 text-center cursor-pointer transition-colors", isDragging && "border-black bg-neutral-50", error && "border-red-500 animate-shake", - !isDragging && !error && "border-neutral-300 hover:border-neutral-400" + !isDragging && !error && "border-neutral-200 hover:border-neutral-400" )} > +
{post.cover_image && ( -
+
{post.title}
@@ -37,7 +37,7 @@ export function PostCard({ post, isOwner = false }: PostCardProps) { -

+

{post.title} {isOwner && !post.published && ( (Draft) @@ -45,16 +45,16 @@ export function PostCard({ post, isOwner = false }: PostCardProps) {

-

+

{post.content.slice(0, 200).replace(/[#*`]/g, "")} {post.content.length > 200 ? "..." : ""}

- Read More + Read More
diff --git a/src/components/post/PostContent.tsx b/src/components/post/PostContent.tsx index 54fdd58..8bd8a9e 100644 --- a/src/components/post/PostContent.tsx +++ b/src/components/post/PostContent.tsx @@ -10,7 +10,7 @@ interface PostContentProps { export function PostContent({ content }: PostContentProps) { return ( -
+
), pre: ({ children }) => ( -
+            
               {children}
             
), diff --git a/src/components/post/PostMeta.tsx b/src/components/post/PostMeta.tsx index 276f1ca..bd69388 100644 --- a/src/components/post/PostMeta.tsx +++ b/src/components/post/PostMeta.tsx @@ -14,7 +14,7 @@ export function PostMeta({ date }: PostMetaProps) { }; return ( -