diff --git a/README.md b/README.md index a08e159..710077f 100644 --- a/README.md +++ b/README.md @@ -85,3 +85,4 @@ A simple blogging platform built with Next.js, shadcn/ui and Tailwind CSS. - [ ] Implement a logout animation on `/admin/logout` - [ ] Add a post list w/ management options on `/admin/posts` - [ ] Add a user list w/ management options in `/admin/users` +- [ ] Better error handling in `server/index.js` diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index d09df91..7132229 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -2,6 +2,7 @@ export const dynamic = 'force-dynamic'; import { useState, useEffect } from 'react'; +import strings from "@/strings.json" export default function AdminLayout({ children }: { children: React.ReactNode }) { const [loading, setLoading] = useState(true); @@ -9,7 +10,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) useEffect(() => { if (window.location.pathname.startsWith('/admin/login')) { - console.log("[i] Detected login page, skipping validation"); + console.log(strings.logsDetectedLoginPage); setAuthorized(true); setLoading(false); return; @@ -24,7 +25,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) const validate = async () => { const key = cookies.key; if (!key) { - console.log("[!] No key found, clearing cookies and redirecting to login"); + console.log(strings.errorsNoKeyFoundFancy); document.cookie.split(';').forEach((cookie) => { const [name] = cookie.split('='); document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`; @@ -41,16 +42,16 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) }); if (!response.ok) { - console.log('[!] Failed to check key, skipping validation and clearing cookie'); + console.log(strings.errorsFailedKeyCheckFancy); document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; window.location.href = '/admin/login'; } else { const data = await response.json() if (data.success) { - console.log("[✓] Key is valid"); + console.log(strings.logsKeyIsValid); setAuthorized(true); } else { - console.log("[✖] Key is invalid, clearing cookie and redirecting to login"); + console.log(strings.errorsKeyInvalidFancy); document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; window.location.href = '/admin/login'; } diff --git a/app/admin/page.tsx b/app/admin/page.tsx index c8c108f..7cbe72b 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -15,7 +15,7 @@ export default function Home() { const [userCtCardLoading, setUserCtCardLoading] = useState(true); useEffect(() => { - console.log("[i] Calculating post count..."); + console.log(strings.logsCalculatingPostCt); (async () => { try { const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; @@ -34,9 +34,9 @@ export default function Home() { }); if (!res.ok) { - alert('Failed to fetch total post count'); + alert(strings.errorsFetchTotalPostCtErr); setPostCardError(true); - throw new Error(`Failed to fetch total post count: ${res.status}`); + throw new Error(`${strings.errorsFetchTotalPostCtErr}: ${res.status}`); } const data = await res.json(); @@ -47,25 +47,25 @@ export default function Home() { setPostCardLoading(false); throw new Error(data.message); } else { - alert('Unknown error occurred'); + alert(strings.errorsUnknownError); setPostCardError(true); setPostCardLoading(false); - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else if (data.count) { - console.log("[✓] Total posts:", data.count); + console.log(strings.logsTotalPosts, data.count); setTotalPosts(data.count); setPostCardLoading(false); } } catch (error) { - alert('Error fetching total post count'); + alert(strings.errorsFetchTotalPostCtErr); setPostCardError(true); setPostCardLoading(false); - console.error('[!] Failed to fetch total post count:', error); + console.error(strings.errorsFetchTotalPostCtErrFancy, error); } })(); - console.log("[i] Calculating user count..."); + console.log(strings.logsCalculatingUserCt); (async () => { try { const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; @@ -84,9 +84,9 @@ export default function Home() { }); if (!res.ok) { - alert('Failed to fetch total user count'); + alert(strings.errorsFetchTotalUserCtErr); setUserCtCardError(true); - throw new Error(`Failed to fetch total user count: ${res.status}`); + throw new Error(`${strings.errorsFetchTotalUserCtErr}: ${res.status}`); } const data = await res.json(); @@ -97,21 +97,21 @@ export default function Home() { setUserCtCardLoading(false); throw new Error(data.message); } else { - alert('Unknown error occurred'); + alert(strings.errorsUnknownError); setUserCtCardError(true); setUserCtCardLoading(false); - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else if (data.count) { - console.log("[✓] Total users:", data.count); + console.log(strings.logsTotalUsers, data.count); setTotalUsers(data.count); setUserCtCardLoading(false); } } catch (error) { - alert('Error fetching total user count'); + alert(strings.errorsFetchTotalUserCtErr); setUserCtCardError(true); setUserCtCardLoading(false); - console.error('[!] Failed to fetch total user count:', error); + console.error(strings.errorsFetchTotalUserCtErrFancy, error); } })(); }, []); @@ -130,7 +130,7 @@ export default function Home() { ) : userCtCardError ? (
-

Error

+

{strings.errorsSuperGeneric}

) : ( totalUsers @@ -149,7 +149,7 @@ export default function Home() { ) : postCardError ? (
-

Error

+

{strings.errorsSuperGeneric}

) : ( totalPosts @@ -162,19 +162,19 @@ export default function Home() {
- Quick Actions + {strings.adminQuickActionsCardTitle} diff --git a/app/admin/posts/new/page.tsx b/app/admin/posts/new/page.tsx index e878fa1..08bc2a6 100644 --- a/app/admin/posts/new/page.tsx +++ b/app/admin/posts/new/page.tsx @@ -43,13 +43,13 @@ export default function CreatePost() { }); if (!response.ok) { - throw new Error('Failed to create post'); + throw new Error(strings.errorsCreatePostFail); } const data = await response.json(); - console.log('Success:', data); + console.log(`${strings.successSuperGeneric}: ${data}`); } catch (error) { - console.error('Error:', error); + console.error(`${strings.errorsSuperGeneric}: ${error}`); } console.log({ title, description, category, slug, content, date }) } diff --git a/app/admin/posts/page.tsx b/app/admin/posts/page.tsx index 295d433..a0bff07 100644 --- a/app/admin/posts/page.tsx +++ b/app/admin/posts/page.tsx @@ -12,7 +12,7 @@ export default function Posts() { const [postCardLoading, setPostCardLoading] = useState(true); useEffect(() => { - console.log("[i] Calculating post count..."); + console.log(strings.logsCalculatingPostCt); (async () => { try { const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; @@ -31,9 +31,9 @@ export default function Posts() { }); if (!res.ok) { - alert('Failed to fetch total post count'); + alert(strings.errorsFetchTotalPostCtErr); setPostCardError(true); - throw new Error(`Failed to fetch total post count: ${res.status}`); + throw new Error(`${strings.errorsFetchTotalPostCtErr}: ${res.status}`); } const data = await res.json(); @@ -44,38 +44,38 @@ export default function Posts() { setPostCardLoading(false); throw new Error(data.message); } else { - alert('Unknown error occurred'); + alert(strings.errorsUnknownError); setPostCardError(true); setPostCardLoading(false); - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else if (data.count) { - console.log("[✓] Total posts:", data.count); + console.log(strings.logsTotalPosts, data.count); setTotalPosts(data.count); setPostCardLoading(false); } } catch (error) { - alert('Error fetching total post count'); + alert(strings.errorsFetchTotalPostCtErr); setPostCardError(true); setPostCardLoading(false); - console.error('[!] Failed to fetch total post count:', error); + console.error(strings.errorsFetchTotalPostCtErrFancy, error); } })(); }, []); return (
-

Posts

+

{strings.postsHeader}

- Quick Actions + {strings.adminQuickActionsCardTitle} @@ -90,7 +90,7 @@ export default function Posts() { ) : postCardError ? (
-

Error

+

{strings.errorsSuperGeneric}

) : ( totalPosts diff --git a/app/admin/users/new/page.tsx b/app/admin/users/new/page.tsx index 347d946..eee7625 100644 --- a/app/admin/users/new/page.tsx +++ b/app/admin/users/new/page.tsx @@ -24,13 +24,13 @@ export default function CreateUser() { }); if (!response.ok) { - throw new Error('Failed to create user'); + throw new Error(strings.errorsCreateUserFail); } const data = await response.json(); - console.log('Success:', data); + console.log(`${strings.successSuperGeneric}: ${data}`); } catch (error) { - console.error('Error:', error); + console.error(`${strings.errorsSuperGeneric}: ${error}`); } console.log({ username, email, password }) } diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx index 2995340..17e61ac 100644 --- a/app/admin/users/page.tsx +++ b/app/admin/users/page.tsx @@ -12,7 +12,7 @@ export default function Users() { const [userCtCardLoading, setUserCtCardLoading] = useState(true); useEffect(() => { - console.log("[i] Calculating user count..."); + console.log(strings.logsCalculatingUserCt); (async () => { try { const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; @@ -31,9 +31,9 @@ export default function Users() { }); if (!res.ok) { - alert('Failed to fetch total user count'); + alert(strings.errorsFetchTotalUserCtErr); setUserCtCardError(true); - throw new Error(`Failed to fetch total user count: ${res.status}`); + throw new Error(`${strings.errorsFetchTotalUserCtErr}: ${res.status}`); } const data = await res.json(); @@ -44,21 +44,21 @@ export default function Users() { setUserCtCardLoading(false); throw new Error(data.message); } else { - alert('Unknown error occurred'); + alert(strings.errorsUnknownError); setUserCtCardError(true); setUserCtCardLoading(false); - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else if (data.count) { - console.log("[✓] Total users:", data.count); + console.log(strings.logsTotalUsers, data.count); setTotalUsers(data.count); setUserCtCardLoading(false); } } catch (error) { - alert('Error fetching total user count'); + alert(strings.errorsFetchTotalUserCtErr); setUserCtCardError(true); setUserCtCardLoading(false); - console.error('[!] Failed to fetch total user count:', error); + console.error(strings.errorsFetchTotalUserCtErrFancy, error); } })(); }, []); @@ -69,13 +69,13 @@ export default function Users() {
- Quick Actions + {strings.adminQuickActionsCardTitle} @@ -90,7 +90,7 @@ export default function Users() { ) : userCtCardError ? (
-

Error

+

{strings.errorsSuperGeneric}

) : ( totalUsers diff --git a/app/categories/page.tsx b/app/categories/page.tsx index d95da03..513a7d0 100644 --- a/app/categories/page.tsx +++ b/app/categories/page.tsx @@ -36,8 +36,8 @@ export default function Home() { setLoading(false) }) .catch((error) => { - console.error("[!] Error fetching data:", error) - setError("Failed to fetch data") + console.error(strings.errorsFetchDataErrCtxFancy, error) + setError(strings.errorsFetchDataFail) setLoading(false) }) }, []) diff --git a/app/category/[slug]/page.tsx b/app/category/[slug]/page.tsx index 4cc0b02..00ba658 100644 --- a/app/category/[slug]/page.tsx +++ b/app/category/[slug]/page.tsx @@ -2,11 +2,18 @@ import { useEffect, useState } from 'react'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" import Link from "next/link" import strings from "@/strings.json" import { formatDistanceToNow, format } from 'date-fns' +type Post = { + id: string; + title: string; + description: string; + date: number; + slug: string; +}; + export default function CategorySlug() { const [posts, setPosts] = useState([]); const [error, setError] = useState(''); @@ -14,7 +21,7 @@ export default function CategorySlug() { const [loading, setLoading] = useState(true); useEffect(() => { - console.log("[i] Fetching post list..."); + console.log(strings.logsFetchPostList); (async () => { try { const catReq = await fetch(`http://localhost:3001/api/categories/fetchList`, { @@ -26,49 +33,53 @@ export default function CategorySlug() { }); if (!catReq.ok) { - throw new Error(`Failed to fetch category list: ${catReq.status}`); + throw new Error(`${strings.errorsFetchCategoryListFailCtx} ${catReq.status}`); } if (!postReq.ok) { - throw new Error(`Failed to fetch post list: ${postReq.status}`); + throw new Error(`${strings.errorsFetchPostListFailCtx} ${postReq.status}`); } const catData = await catReq.json(); const postData = await postReq.json(); if (!catData) { - setError('Failed to fetch category list'); + setError(strings.errorsFetchCategoryListFail); } else { - console.log("[✓] Fetched categories"); + console.log(strings.logsOnFetchedCategory); const slug = window.location.pathname.split('/').slice(-1)[0]; const category = catData.categories.find((cat: { slug: string }) => cat.slug === slug); if (category) { - console.log(`[✓] Found category: ${category.name}`); + console.log(`${strings.logsOnFoundCategory} ${category.name}`); setCategory(category.name); if (postData.success === false) { if (postData.message) { throw new Error(postData.message); } else { - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else { - const sortedPosts = postData.posts.sort((a, b) => b.date - a.date); + const sortedPosts = postData.posts.sort((a: Post, b: Post) => b.date - a.date); setPosts(sortedPosts); } } else { - setError('Could not find requested category'); - throw new Error(`Category with slug "${slug}" not found`); + setError(strings.errorsFetchCategoryNotFound); + throw new Error(strings.errorsFetchCategoryNotFound); } } } catch (error) { - console.error('[!] Error fetching post list:', error); - setError('Failed to fetch post list. Please try again later.'); + console.error(strings.errorsFetchPostListErrCtxFancy, error); + setError(strings.errorsFetchPostListErr); } finally { setLoading(false); } })(); }, []); - const formatDate = (timestamp) => { + interface FormatDate { + (timestamp: number): string; + } + + const formatDate: FormatDate = (timestamp) => { const date = new Date(timestamp * 1000); const now = new Date(); if (date.getFullYear() !== now.getFullYear()) { diff --git a/app/page.tsx b/app/page.tsx index 6431e6f..6ba374a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -22,7 +22,7 @@ export default function Home() { const [loading, setLoading] = useState(true); useEffect(() => { - console.log("[i] Fetching post list..."); + console.log(strings.logsFetchPostList); (async () => { try { const res = await fetch(`http://localhost:3001/api/posts/fetchList`, { @@ -30,7 +30,7 @@ export default function Home() { }); if (!res.ok) { - throw new Error(`Failed to fetch post list: ${res.status}`); + throw new Error(`${strings.errorsFetchPostListFailCtx} ${res.status}`); } const data = await res.json(); @@ -38,15 +38,15 @@ export default function Home() { if (data.message) { throw new Error(data.message); } else { - throw new Error('Unknown error occurred'); + throw new Error(strings.errorsUnknownError); } } else { const sortedPosts: Post[] = data.posts.sort((a: Post, b: Post) => b.date - a.date); setPosts(sortedPosts); } } catch (error) { - console.error('[!] Error fetching post list:', error); - setError('Failed to fetch post list. Please try again later.'); + console.error(strings.errorsFetchPostListErrCtx, error); + setError(strings.errorsFetchPostListErr); } finally { setLoading(false); } diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx index f0c8e5d..cbbe883 100644 --- a/app/posts/[slug]/page.tsx +++ b/app/posts/[slug]/page.tsx @@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm'; import { useParams } from 'next/navigation'; import Image from 'next/image'; import { Tag } from 'lucide-react'; +import strings from "@/strings.json" export default function PostPage() { const [markdown, setMarkdown] = useState(''); @@ -24,18 +25,14 @@ export default function PostPage() { } function setPostData(postData: PostData) { - setPostTitle(postData.title || 'Untitled Post'); + setPostTitle(postData.title || strings.blankPostTitlePlaceholder); if (postData.date) { - console.log("[i] Date:", postData.date); const date = new Date(Number(postData.date) * 1000) - console.log("[i] Date object:", date); const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - console.log("[i] Date options:", options); - console.log("[i] Formatted date:", date.toLocaleDateString(undefined, options)); setPostDate(date.toLocaleDateString(undefined, options)); } else { - setPostDate('an unknown date'); + setPostDate(strings.blankPostDatePlaceholder); } if (postData.category) { @@ -43,11 +40,11 @@ export default function PostPage() { } else if (postData.message) { setPostCategory(postData.message); } else { - setPostCategory("Error"); + setPostCategory(strings.errorsSuperGeneric); } } - console.log("[i] Navigated to slug: ", slug); + console.log(strings.logsOnSlugNavigate, slug); (async () => { try { const res = await fetch(`http://localhost:3001/api/posts/get/${slug}`, { @@ -64,8 +61,8 @@ export default function PostPage() { throw new Error(data.message); } else { setLoading(false); - setError('Unknown error occurred'); - throw new Error('Unknown error occurred'); + setError(strings.errorsUnknownError); + throw new Error(strings.errorsUnknownError); } } } else { @@ -81,7 +78,7 @@ export default function PostPage() { if (postData.message) { setPostData(postData); } else { - setPostCategory("Error"); + setPostCategory(strings.errorsSuperGeneric); } } @@ -89,9 +86,9 @@ export default function PostPage() { setLoading(false); } } catch (error) { - console.error('Error fetching post:', error); - setError('Could not load the post.'); - setMarkdown('# Error\n\nCould not load the post.'); + console.error(strings.errorsFetchPostErr, ':', error); + setError(strings.errorsFetchPostErr); + setMarkdown(strings.errorsFetchPostErrMd); } })(); }, [slug]); @@ -113,7 +110,7 @@ export default function PostPage() { return (

{postTitle}

-

Published on {postDate}

+

{strings.postPublishedOnLabel} {postDate}

{postCategory} diff --git a/bun.lockb b/bun.lockb index 329fef5..94d963f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/login-form.tsx b/components/login-form.tsx index 1236a97..3315f79 100644 --- a/components/login-form.tsx +++ b/components/login-form.tsx @@ -3,15 +3,10 @@ import { useState, useEffect } from "react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" +import strings from "@/strings.json" export function LoginForm({ className, @@ -30,7 +25,7 @@ export function LoginForm({ }, {} as Record); if (cookies.key) { - console.log('[i] Key found in browser cookies, checking validity'); + console.log(strings.logsFoundKey); try { const response = await fetch('http://localhost:3001/api/admin/validate', { method: 'POST', @@ -41,17 +36,17 @@ export function LoginForm({ }) if (!response.ok) { - console.log('[!] Failed to check key, skipping validation and clearing cookie'); + console.log(strings.errorsFailedKeyCheckFancy); document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; } else { const data = await response.json() if (data.valid) { // key exists and is valid, user has no reason to use login - console.log("[✓] Key is valid, redirecting to admin panel"); + console.log(strings.logsAdminLoginKeyIsValid); window.location.href = '/admin'; } else { // key exists, but the server reports its not the latest key - console.log("[!] Key is invalid, clearing cookie and redirecting to login"); + console.log(strings.errorsFailedKeyCheckFancy); document.cookie.split(";").forEach((cookie) => { document.cookie = cookie .replace(/^ +/, "") @@ -61,8 +56,8 @@ export function LoginForm({ } } } catch (error) { - console.error('[!]', error) - setError('Failed to connect to the server. Please try again later.') + console.error('[!]', error); + setError(strings.errorsConnectionFail); } } }; @@ -85,12 +80,12 @@ export function LoginForm({ }) if (!response.ok) { - console.log('[!] Failed to login'); - setError('An unknown error occurred. Please try again later.') + console.log(strings.errorsLoginFailFancy); + setError(strings.errorsUnknownErrorLong) } else { const data = await response.json() if (data.success && data.key) { - console.log("[✓] Login successful, redirecting to admin panel"); + console.log(strings.logsAdminLoginSuccessful); document.cookie = `key=${data.key}; path=/; secure; samesite=strict`; document.cookie = `username=${username}; path=/; secure; samesite=strict`; document.location.href = '/admin'; @@ -98,13 +93,13 @@ export function LoginForm({ if (!data.success && data.message) { setError(data.message) } else { - setError('An unknown error occurred. Please try again later.') + setError(strings.errorsUnknownErrorLong) } } } } catch (error) { console.error('[i]', error) - setError('Failed to connect to the server. Please try again later.') + setError(strings.errorsConnectionFail) } } @@ -112,9 +107,9 @@ export function LoginForm({
- Administration Panel + {strings.adminLoginCardTitle} - Please authenticate with your credentials. + {strings.adminLoginCardDescription} @@ -122,7 +117,7 @@ export function LoginForm({
- +
- +
diff --git a/components/navigation/Navbar.tsx b/components/navigation/Navbar.tsx index c51c2aa..ddcc3a6 100644 --- a/components/navigation/Navbar.tsx +++ b/components/navigation/Navbar.tsx @@ -48,11 +48,11 @@ export function Navbar() {
- +
diff --git a/components/navigation/Sidebar.tsx b/components/navigation/Sidebar.tsx index b48f548..3619b36 100644 --- a/components/navigation/Sidebar.tsx +++ b/components/navigation/Sidebar.tsx @@ -22,31 +22,31 @@ export function Sidebar() { const [uniqueCategories, setUniqueCategories] = useState<{ name: string; slug: string }[]>([]); useEffect(() => { - console.log("[i] Fetching post list..."); + console.log(strings.logsFetchPostList); fetch('http://localhost:3001/api/posts/fetchList') .then(response => response.json()) .then(data => { if (!data.posts) { - throw new Error('[!] Failed to fetch posts'); + throw new Error(strings.errorsFetchPostsFailFancy); } - console.log("[✓] Fetched posts"); + console.log(strings.logsOnFetchedPosts); setPosts(data.posts); setLoadingPosts(false); }) .catch(error => { console.error(error); - setError(`[!] Error fetching posts: ${error.message}`); + setError(`${strings.errorsFetchPostsFailFancy}: ${error.message}`); setLoadingPosts(false); }); - console.log("[i] Fetching category list..."); + console.log(strings.logsFetchCategoryList); fetch('http://localhost:3001/api/categories/fetchList') .then(response => response.json()) .then(data => { if (!data.categories) { - throw new Error('Failed to fetch categories'); + throw new Error(strings.errorsFetchCategoryListFail); } - console.log("[✓] Fetched categories"); + console.log(strings.logsOnFetchedCategory); const categories = data.categories.map((cat: { name: string, slug: string }) => ({ name: cat.name, slug: cat.slug, @@ -56,7 +56,7 @@ export function Sidebar() { }) .catch(error => { console.error(error); - setError(`[!] Error fetching categories: ${error.message}`); + setError(`${strings.errorsFetchCategoriesErrCtxFancy} ${error.message}`); setLoadingCategories(false); }); }, []); @@ -64,12 +64,12 @@ export function Sidebar() { return (