migrate all strings to strings.json, reorganize strings.json, bump packages, add todo, rm unnecessary import, minor error fixes

This commit is contained in:
Aidan 2025-01-30 19:59:24 -05:00
parent 05f9be8493
commit d52330d191
20 changed files with 265 additions and 163 deletions

View File

@ -85,3 +85,4 @@ A simple blogging platform built with Next.js, shadcn/ui and Tailwind CSS.
- [ ] Implement a logout animation on `/admin/logout` - [ ] Implement a logout animation on `/admin/logout`
- [ ] Add a post list w/ management options on `/admin/posts` - [ ] Add a post list w/ management options on `/admin/posts`
- [ ] Add a user list w/ management options in `/admin/users` - [ ] Add a user list w/ management options in `/admin/users`
- [ ] Better error handling in `server/index.js`

View File

@ -2,6 +2,7 @@
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import strings from "@/strings.json"
export default function AdminLayout({ children }: { children: React.ReactNode }) { export default function AdminLayout({ children }: { children: React.ReactNode }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -9,7 +10,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
useEffect(() => { useEffect(() => {
if (window.location.pathname.startsWith('/admin/login')) { if (window.location.pathname.startsWith('/admin/login')) {
console.log("[i] Detected login page, skipping validation"); console.log(strings.logsDetectedLoginPage);
setAuthorized(true); setAuthorized(true);
setLoading(false); setLoading(false);
return; return;
@ -24,7 +25,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
const validate = async () => { const validate = async () => {
const key = cookies.key; const key = cookies.key;
if (!key) { if (!key) {
console.log("[!] No key found, clearing cookies and redirecting to login"); console.log(strings.errorsNoKeyFoundFancy);
document.cookie.split(';').forEach((cookie) => { document.cookie.split(';').forEach((cookie) => {
const [name] = cookie.split('='); const [name] = cookie.split('=');
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`; 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) { 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'; document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
window.location.href = '/admin/login'; window.location.href = '/admin/login';
} else { } else {
const data = await response.json() const data = await response.json()
if (data.success) { if (data.success) {
console.log("[✓] Key is valid"); console.log(strings.logsKeyIsValid);
setAuthorized(true); setAuthorized(true);
} else { } 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'; document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
window.location.href = '/admin/login'; window.location.href = '/admin/login';
} }

View File

@ -15,7 +15,7 @@ export default function Home() {
const [userCtCardLoading, setUserCtCardLoading] = useState(true); const [userCtCardLoading, setUserCtCardLoading] = useState(true);
useEffect(() => { useEffect(() => {
console.log("[i] Calculating post count..."); console.log(strings.logsCalculatingPostCt);
(async () => { (async () => {
try { try {
const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || '';
@ -34,9 +34,9 @@ export default function Home() {
}); });
if (!res.ok) { if (!res.ok) {
alert('Failed to fetch total post count'); alert(strings.errorsFetchTotalPostCtErr);
setPostCardError(true); 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(); const data = await res.json();
@ -47,25 +47,25 @@ export default function Home() {
setPostCardLoading(false); setPostCardLoading(false);
throw new Error(data.message); throw new Error(data.message);
} else { } else {
alert('Unknown error occurred'); alert(strings.errorsUnknownError);
setPostCardError(true); setPostCardError(true);
setPostCardLoading(false); setPostCardLoading(false);
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else if (data.count) { } else if (data.count) {
console.log("[✓] Total posts:", data.count); console.log(strings.logsTotalPosts, data.count);
setTotalPosts(data.count); setTotalPosts(data.count);
setPostCardLoading(false); setPostCardLoading(false);
} }
} catch (error) { } catch (error) {
alert('Error fetching total post count'); alert(strings.errorsFetchTotalPostCtErr);
setPostCardError(true); setPostCardError(true);
setPostCardLoading(false); 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 () => { (async () => {
try { try {
const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || '';
@ -84,9 +84,9 @@ export default function Home() {
}); });
if (!res.ok) { if (!res.ok) {
alert('Failed to fetch total user count'); alert(strings.errorsFetchTotalUserCtErr);
setUserCtCardError(true); 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(); const data = await res.json();
@ -97,21 +97,21 @@ export default function Home() {
setUserCtCardLoading(false); setUserCtCardLoading(false);
throw new Error(data.message); throw new Error(data.message);
} else { } else {
alert('Unknown error occurred'); alert(strings.errorsUnknownError);
setUserCtCardError(true); setUserCtCardError(true);
setUserCtCardLoading(false); setUserCtCardLoading(false);
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else if (data.count) { } else if (data.count) {
console.log("[✓] Total users:", data.count); console.log(strings.logsTotalUsers, data.count);
setTotalUsers(data.count); setTotalUsers(data.count);
setUserCtCardLoading(false); setUserCtCardLoading(false);
} }
} catch (error) { } catch (error) {
alert('Error fetching total user count'); alert(strings.errorsFetchTotalUserCtErr);
setUserCtCardError(true); setUserCtCardError(true);
setUserCtCardLoading(false); 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 ? ( ) : userCtCardError ? (
<div className="flex items-center text-red-500"> <div className="flex items-center text-red-500">
<CircleAlert /> <CircleAlert />
<p className="text-base ml-1.5">Error</p> <p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
</div> </div>
) : ( ) : (
totalUsers totalUsers
@ -149,7 +149,7 @@ export default function Home() {
) : postCardError ? ( ) : postCardError ? (
<div className="flex items-center text-red-500"> <div className="flex items-center text-red-500">
<CircleAlert /> <CircleAlert />
<p className="text-base ml-1.5">Error</p> <p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
</div> </div>
) : ( ) : (
totalPosts totalPosts
@ -162,19 +162,19 @@ export default function Home() {
<div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4"> <div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4">
<Card className="flex flex-col justify-between"> <Card className="flex flex-col justify-between">
<CardHeader> <CardHeader>
<CardTitle>Quick Actions</CardTitle> <CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid gap-4"> <CardContent className="grid gap-4">
<Button className="w-full justify-start" variant="outline" asChild> <Button className="w-full justify-start" variant="outline" asChild>
<a href="/admin/posts/new"> <a href="/admin/posts/new">
<PlusCircle className="mr-2 h-4 w-4" /> <PlusCircle className="mr-2 h-4 w-4" />
New Post {strings.createPostButtonText}
</a> </a>
</Button> </Button>
<Button className="w-full justify-start" variant="outline" asChild> <Button className="w-full justify-start" variant="outline" asChild>
<a href="/admin/users/new"> <a href="/admin/users/new">
<UserPlus className="mr-2 h-4 w-4" /> <UserPlus className="mr-2 h-4 w-4" />
New User {strings.createUserButtonText}
</a> </a>
</Button> </Button>
</CardContent> </CardContent>

View File

@ -43,13 +43,13 @@ export default function CreatePost() {
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to create post'); throw new Error(strings.errorsCreatePostFail);
} }
const data = await response.json(); const data = await response.json();
console.log('Success:', data); console.log(`${strings.successSuperGeneric}: ${data}`);
} catch (error) { } catch (error) {
console.error('Error:', error); console.error(`${strings.errorsSuperGeneric}: ${error}`);
} }
console.log({ title, description, category, slug, content, date }) console.log({ title, description, category, slug, content, date })
} }

View File

@ -12,7 +12,7 @@ export default function Posts() {
const [postCardLoading, setPostCardLoading] = useState(true); const [postCardLoading, setPostCardLoading] = useState(true);
useEffect(() => { useEffect(() => {
console.log("[i] Calculating post count..."); console.log(strings.logsCalculatingPostCt);
(async () => { (async () => {
try { try {
const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || '';
@ -31,9 +31,9 @@ export default function Posts() {
}); });
if (!res.ok) { if (!res.ok) {
alert('Failed to fetch total post count'); alert(strings.errorsFetchTotalPostCtErr);
setPostCardError(true); 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(); const data = await res.json();
@ -44,38 +44,38 @@ export default function Posts() {
setPostCardLoading(false); setPostCardLoading(false);
throw new Error(data.message); throw new Error(data.message);
} else { } else {
alert('Unknown error occurred'); alert(strings.errorsUnknownError);
setPostCardError(true); setPostCardError(true);
setPostCardLoading(false); setPostCardLoading(false);
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else if (data.count) { } else if (data.count) {
console.log("[✓] Total posts:", data.count); console.log(strings.logsTotalPosts, data.count);
setTotalPosts(data.count); setTotalPosts(data.count);
setPostCardLoading(false); setPostCardLoading(false);
} }
} catch (error) { } catch (error) {
alert('Error fetching total post count'); alert(strings.errorsFetchTotalPostCtErr);
setPostCardError(true); setPostCardError(true);
setPostCardLoading(false); setPostCardLoading(false);
console.error('[!] Failed to fetch total post count:', error); console.error(strings.errorsFetchTotalPostCtErrFancy, error);
} }
})(); })();
}, []); }, []);
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<h1 className="text-4xl font-bold text-primary">Posts</h1> <h1 className="text-4xl font-bold text-primary">{strings.postsHeader}</h1>
<div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4"> <div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4">
<Card className="flex flex-col justify-between"> <Card className="flex flex-col justify-between">
<CardHeader> <CardHeader>
<CardTitle>Quick Actions</CardTitle> <CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid gap-4"> <CardContent className="grid gap-4">
<Button className="w-full justify-start" variant="outline" asChild> <Button className="w-full justify-start" variant="outline" asChild>
<a href="/admin/post"> <a href="/admin/post">
<PlusCircle className="mr-2 h-4 w-4" /> <PlusCircle className="mr-2 h-4 w-4" />
New Post {strings.createPostButtonText}
</a> </a>
</Button> </Button>
</CardContent> </CardContent>
@ -90,7 +90,7 @@ export default function Posts() {
) : postCardError ? ( ) : postCardError ? (
<div className="flex items-center text-red-500"> <div className="flex items-center text-red-500">
<CircleAlert /> <CircleAlert />
<p className="text-base ml-1.5">Error</p> <p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
</div> </div>
) : ( ) : (
totalPosts totalPosts

View File

@ -24,13 +24,13 @@ export default function CreateUser() {
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to create user'); throw new Error(strings.errorsCreateUserFail);
} }
const data = await response.json(); const data = await response.json();
console.log('Success:', data); console.log(`${strings.successSuperGeneric}: ${data}`);
} catch (error) { } catch (error) {
console.error('Error:', error); console.error(`${strings.errorsSuperGeneric}: ${error}`);
} }
console.log({ username, email, password }) console.log({ username, email, password })
} }

View File

@ -12,7 +12,7 @@ export default function Users() {
const [userCtCardLoading, setUserCtCardLoading] = useState(true); const [userCtCardLoading, setUserCtCardLoading] = useState(true);
useEffect(() => { useEffect(() => {
console.log("[i] Calculating user count..."); console.log(strings.logsCalculatingUserCt);
(async () => { (async () => {
try { try {
const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || ''; const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || '';
@ -31,9 +31,9 @@ export default function Users() {
}); });
if (!res.ok) { if (!res.ok) {
alert('Failed to fetch total user count'); alert(strings.errorsFetchTotalUserCtErr);
setUserCtCardError(true); 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(); const data = await res.json();
@ -44,21 +44,21 @@ export default function Users() {
setUserCtCardLoading(false); setUserCtCardLoading(false);
throw new Error(data.message); throw new Error(data.message);
} else { } else {
alert('Unknown error occurred'); alert(strings.errorsUnknownError);
setUserCtCardError(true); setUserCtCardError(true);
setUserCtCardLoading(false); setUserCtCardLoading(false);
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else if (data.count) { } else if (data.count) {
console.log("[✓] Total users:", data.count); console.log(strings.logsTotalUsers, data.count);
setTotalUsers(data.count); setTotalUsers(data.count);
setUserCtCardLoading(false); setUserCtCardLoading(false);
} }
} catch (error) { } catch (error) {
alert('Error fetching total user count'); alert(strings.errorsFetchTotalUserCtErr);
setUserCtCardError(true); setUserCtCardError(true);
setUserCtCardLoading(false); setUserCtCardLoading(false);
console.error('[!] Failed to fetch total user count:', error); console.error(strings.errorsFetchTotalUserCtErrFancy, error);
} }
})(); })();
}, []); }, []);
@ -69,13 +69,13 @@ export default function Users() {
<div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4"> <div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4">
<Card className="flex flex-col justify-between"> <Card className="flex flex-col justify-between">
<CardHeader> <CardHeader>
<CardTitle>Quick Actions</CardTitle> <CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid gap-4"> <CardContent className="grid gap-4">
<Button className="w-full justify-start" variant="outline" asChild> <Button className="w-full justify-start" variant="outline" asChild>
<a href="/admin/users/new"> <a href="/admin/users/new">
<UserPlus className="mr-2 h-4 w-4" /> <UserPlus className="mr-2 h-4 w-4" />
New User {strings.createUserButtonText}
</a> </a>
</Button> </Button>
</CardContent> </CardContent>
@ -90,7 +90,7 @@ export default function Users() {
) : userCtCardError ? ( ) : userCtCardError ? (
<div className="flex items-center text-red-500"> <div className="flex items-center text-red-500">
<CircleAlert /> <CircleAlert />
<p className="text-base ml-1.5">Error</p> <p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
</div> </div>
) : ( ) : (
totalUsers totalUsers

View File

@ -36,8 +36,8 @@ export default function Home() {
setLoading(false) setLoading(false)
}) })
.catch((error) => { .catch((error) => {
console.error("[!] Error fetching data:", error) console.error(strings.errorsFetchDataErrCtxFancy, error)
setError("Failed to fetch data") setError(strings.errorsFetchDataFail)
setLoading(false) setLoading(false)
}) })
}, []) }, [])

View File

@ -2,11 +2,18 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import Link from "next/link" import Link from "next/link"
import strings from "@/strings.json" import strings from "@/strings.json"
import { formatDistanceToNow, format } from 'date-fns' import { formatDistanceToNow, format } from 'date-fns'
type Post = {
id: string;
title: string;
description: string;
date: number;
slug: string;
};
export default function CategorySlug() { export default function CategorySlug() {
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [error, setError] = useState(''); const [error, setError] = useState('');
@ -14,7 +21,7 @@ export default function CategorySlug() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
console.log("[i] Fetching post list..."); console.log(strings.logsFetchPostList);
(async () => { (async () => {
try { try {
const catReq = await fetch(`http://localhost:3001/api/categories/fetchList`, { const catReq = await fetch(`http://localhost:3001/api/categories/fetchList`, {
@ -26,49 +33,53 @@ export default function CategorySlug() {
}); });
if (!catReq.ok) { if (!catReq.ok) {
throw new Error(`Failed to fetch category list: ${catReq.status}`); throw new Error(`${strings.errorsFetchCategoryListFailCtx} ${catReq.status}`);
} }
if (!postReq.ok) { 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 catData = await catReq.json();
const postData = await postReq.json(); const postData = await postReq.json();
if (!catData) { if (!catData) {
setError('Failed to fetch category list'); setError(strings.errorsFetchCategoryListFail);
} else { } else {
console.log("[✓] Fetched categories"); console.log(strings.logsOnFetchedCategory);
const slug = window.location.pathname.split('/').slice(-1)[0]; const slug = window.location.pathname.split('/').slice(-1)[0];
const category = catData.categories.find((cat: { slug: string }) => cat.slug === slug); const category = catData.categories.find((cat: { slug: string }) => cat.slug === slug);
if (category) { if (category) {
console.log(`[✓] Found category: ${category.name}`); console.log(`${strings.logsOnFoundCategory} ${category.name}`);
setCategory(category.name); setCategory(category.name);
if (postData.success === false) { if (postData.success === false) {
if (postData.message) { if (postData.message) {
throw new Error(postData.message); throw new Error(postData.message);
} else { } else {
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else { } 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); setPosts(sortedPosts);
} }
} else { } else {
setError('Could not find requested category'); setError(strings.errorsFetchCategoryNotFound);
throw new Error(`Category with slug "${slug}" not found`); throw new Error(strings.errorsFetchCategoryNotFound);
} }
} }
} catch (error) { } catch (error) {
console.error('[!] Error fetching post list:', error); console.error(strings.errorsFetchPostListErrCtxFancy, error);
setError('Failed to fetch post list. Please try again later.'); setError(strings.errorsFetchPostListErr);
} finally { } finally {
setLoading(false); setLoading(false);
} }
})(); })();
}, []); }, []);
const formatDate = (timestamp) => { interface FormatDate {
(timestamp: number): string;
}
const formatDate: FormatDate = (timestamp) => {
const date = new Date(timestamp * 1000); const date = new Date(timestamp * 1000);
const now = new Date(); const now = new Date();
if (date.getFullYear() !== now.getFullYear()) { if (date.getFullYear() !== now.getFullYear()) {

View File

@ -22,7 +22,7 @@ export default function Home() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
console.log("[i] Fetching post list..."); console.log(strings.logsFetchPostList);
(async () => { (async () => {
try { try {
const res = await fetch(`http://localhost:3001/api/posts/fetchList`, { const res = await fetch(`http://localhost:3001/api/posts/fetchList`, {
@ -30,7 +30,7 @@ export default function Home() {
}); });
if (!res.ok) { 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(); const data = await res.json();
@ -38,15 +38,15 @@ export default function Home() {
if (data.message) { if (data.message) {
throw new Error(data.message); throw new Error(data.message);
} else { } else {
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} else { } else {
const sortedPosts: Post[] = data.posts.sort((a: Post, b: Post) => b.date - a.date); const sortedPosts: Post[] = data.posts.sort((a: Post, b: Post) => b.date - a.date);
setPosts(sortedPosts); setPosts(sortedPosts);
} }
} catch (error) { } catch (error) {
console.error('[!] Error fetching post list:', error); console.error(strings.errorsFetchPostListErrCtx, error);
setError('Failed to fetch post list. Please try again later.'); setError(strings.errorsFetchPostListErr);
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import Image from 'next/image'; import Image from 'next/image';
import { Tag } from 'lucide-react'; import { Tag } from 'lucide-react';
import strings from "@/strings.json"
export default function PostPage() { export default function PostPage() {
const [markdown, setMarkdown] = useState(''); const [markdown, setMarkdown] = useState('');
@ -24,18 +25,14 @@ export default function PostPage() {
} }
function setPostData(postData: PostData) { function setPostData(postData: PostData) {
setPostTitle(postData.title || 'Untitled Post'); setPostTitle(postData.title || strings.blankPostTitlePlaceholder);
if (postData.date) { if (postData.date) {
console.log("[i] Date:", postData.date);
const date = new Date(Number(postData.date) * 1000) const date = new Date(Number(postData.date) * 1000)
console.log("[i] Date object:", date);
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; 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)); setPostDate(date.toLocaleDateString(undefined, options));
} else { } else {
setPostDate('an unknown date'); setPostDate(strings.blankPostDatePlaceholder);
} }
if (postData.category) { if (postData.category) {
@ -43,11 +40,11 @@ export default function PostPage() {
} else if (postData.message) { } else if (postData.message) {
setPostCategory(postData.message); setPostCategory(postData.message);
} else { } else {
setPostCategory("Error"); setPostCategory(strings.errorsSuperGeneric);
} }
} }
console.log("[i] Navigated to slug: ", slug); console.log(strings.logsOnSlugNavigate, slug);
(async () => { (async () => {
try { try {
const res = await fetch(`http://localhost:3001/api/posts/get/${slug}`, { const res = await fetch(`http://localhost:3001/api/posts/get/${slug}`, {
@ -64,8 +61,8 @@ export default function PostPage() {
throw new Error(data.message); throw new Error(data.message);
} else { } else {
setLoading(false); setLoading(false);
setError('Unknown error occurred'); setError(strings.errorsUnknownError);
throw new Error('Unknown error occurred'); throw new Error(strings.errorsUnknownError);
} }
} }
} else { } else {
@ -81,7 +78,7 @@ export default function PostPage() {
if (postData.message) { if (postData.message) {
setPostData(postData); setPostData(postData);
} else { } else {
setPostCategory("Error"); setPostCategory(strings.errorsSuperGeneric);
} }
} }
@ -89,9 +86,9 @@ export default function PostPage() {
setLoading(false); setLoading(false);
} }
} catch (error) { } catch (error) {
console.error('Error fetching post:', error); console.error(strings.errorsFetchPostErr, ':', error);
setError('Could not load the post.'); setError(strings.errorsFetchPostErr);
setMarkdown('# Error\n\nCould not load the post.'); setMarkdown(strings.errorsFetchPostErrMd);
} }
})(); })();
}, [slug]); }, [slug]);
@ -113,7 +110,7 @@ export default function PostPage() {
return ( return (
<div> <div>
<h1 className="text-4xl font-bold my-4">{postTitle}</h1> <h1 className="text-4xl font-bold my-4">{postTitle}</h1>
<p className="italic text-muted-foreground">Published on {postDate}</p> <p className="italic text-muted-foreground">{strings.postPublishedOnLabel} {postDate}</p>
<div className="flex flex-wrap gap-2 my-4"> <div className="flex flex-wrap gap-2 my-4">
<span className="border border-white text-bold px-3 py-1 rounded-md"> <span className="border border-white text-bold px-3 py-1 rounded-md">
<Tag className="w-4 h-4 inline-block mr-1" /> {postCategory} <Tag className="w-4 h-4 inline-block mr-1" /> {postCategory}

BIN
bun.lockb

Binary file not shown.

View File

@ -3,15 +3,10 @@
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import strings from "@/strings.json"
export function LoginForm({ export function LoginForm({
className, className,
@ -30,7 +25,7 @@ export function LoginForm({
}, {} as Record<string, string>); }, {} as Record<string, string>);
if (cookies.key) { if (cookies.key) {
console.log('[i] Key found in browser cookies, checking validity'); console.log(strings.logsFoundKey);
try { try {
const response = await fetch('http://localhost:3001/api/admin/validate', { const response = await fetch('http://localhost:3001/api/admin/validate', {
method: 'POST', method: 'POST',
@ -41,17 +36,17 @@ export function LoginForm({
}) })
if (!response.ok) { 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'; document.cookie = 'key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
} else { } else {
const data = await response.json() const data = await response.json()
if (data.valid) { if (data.valid) {
// key exists and is valid, user has no reason to use login // 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'; window.location.href = '/admin';
} else { } else {
// key exists, but the server reports its not the latest key // 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.split(";").forEach((cookie) => {
document.cookie = cookie document.cookie = cookie
.replace(/^ +/, "") .replace(/^ +/, "")
@ -61,8 +56,8 @@ export function LoginForm({
} }
} }
} catch (error) { } catch (error) {
console.error('[!]', error) console.error('[!]', error);
setError('Failed to connect to the server. Please try again later.') setError(strings.errorsConnectionFail);
} }
} }
}; };
@ -85,12 +80,12 @@ export function LoginForm({
}) })
if (!response.ok) { if (!response.ok) {
console.log('[!] Failed to login'); console.log(strings.errorsLoginFailFancy);
setError('An unknown error occurred. Please try again later.') setError(strings.errorsUnknownErrorLong)
} else { } else {
const data = await response.json() const data = await response.json()
if (data.success && data.key) { 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 = `key=${data.key}; path=/; secure; samesite=strict`;
document.cookie = `username=${username}; path=/; secure; samesite=strict`; document.cookie = `username=${username}; path=/; secure; samesite=strict`;
document.location.href = '/admin'; document.location.href = '/admin';
@ -98,13 +93,13 @@ export function LoginForm({
if (!data.success && data.message) { if (!data.success && data.message) {
setError(data.message) setError(data.message)
} else { } else {
setError('An unknown error occurred. Please try again later.') setError(strings.errorsUnknownErrorLong)
} }
} }
} }
} catch (error) { } catch (error) {
console.error('[i]', 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({
<div className={cn("relative flex min-h-screen flex-col", className)} {...props}> <div className={cn("relative flex min-h-screen flex-col", className)} {...props}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-2xl">Administration Panel</CardTitle> <CardTitle className="text-2xl">{strings.adminLoginCardTitle}</CardTitle>
<CardDescription> <CardDescription>
Please authenticate with your credentials. {strings.adminLoginCardDescription}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -122,7 +117,7 @@ export function LoginForm({
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="username">Username</Label> <Label htmlFor="username">{strings.adminLoginUsernameFieldLabel}</Label>
<Input <Input
id="username" id="username"
type="text" type="text"
@ -133,7 +128,7 @@ export function LoginForm({
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<div className="flex items-center"> <div className="flex items-center">
<Label htmlFor="password">Password</Label> <Label htmlFor="password">{strings.adminLoginPasswordFieldLabel}</Label>
</div> </div>
<Input <Input
id="password" id="password"
@ -144,7 +139,7 @@ export function LoginForm({
/> />
</div> </div>
<Button type="submit" className="w-full"> <Button type="submit" className="w-full">
Login {strings.adminLoginButtonText}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -48,11 +48,11 @@ export function Navbar() {
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button variant="ghost" size="icon" className="md:hidden lg:hidden" onClick={toggleSidebar}> <Button variant="ghost" size="icon" className="md:hidden lg:hidden" onClick={toggleSidebar}>
<Menu className="h-5 w-5" /> <Menu className="h-5 w-5" />
<span className="sr-only">Toggle sidebar</span> <span className="sr-only">{strings.screenReaderToggleSidebar}</span>
</Button> </Button>
</div> </div>
<div className="w-full flex-1 md:w-auto md:flex-none"> <div className="w-full flex-1 md:w-auto md:flex-none">
<Input placeholder="Search posts on desktop..." className="h-9 w-full md:hidden lg:hidden" /> <Input placeholder={strings.searchPlaceholder} className="h-9 w-full md:hidden lg:hidden" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,31 +22,31 @@ export function Sidebar() {
const [uniqueCategories, setUniqueCategories] = useState<{ name: string; slug: string }[]>([]); const [uniqueCategories, setUniqueCategories] = useState<{ name: string; slug: string }[]>([]);
useEffect(() => { useEffect(() => {
console.log("[i] Fetching post list..."); console.log(strings.logsFetchPostList);
fetch('http://localhost:3001/api/posts/fetchList') fetch('http://localhost:3001/api/posts/fetchList')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!data.posts) { 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); setPosts(data.posts);
setLoadingPosts(false); setLoadingPosts(false);
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
setError(`[!] Error fetching posts: ${error.message}`); setError(`${strings.errorsFetchPostsFailFancy}: ${error.message}`);
setLoadingPosts(false); setLoadingPosts(false);
}); });
console.log("[i] Fetching category list..."); console.log(strings.logsFetchCategoryList);
fetch('http://localhost:3001/api/categories/fetchList') fetch('http://localhost:3001/api/categories/fetchList')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!data.categories) { 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 }) => ({ const categories = data.categories.map((cat: { name: string, slug: string }) => ({
name: cat.name, name: cat.name,
slug: cat.slug, slug: cat.slug,
@ -56,7 +56,7 @@ export function Sidebar() {
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
setError(`[!] Error fetching categories: ${error.message}`); setError(`${strings.errorsFetchCategoriesErrCtxFancy} ${error.message}`);
setLoadingCategories(false); setLoadingCategories(false);
}); });
}, []); }, []);
@ -64,12 +64,12 @@ export function Sidebar() {
return ( return (
<aside className="fixed left-3 md:left-2 lg:left-5 top-15 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto bg-background px-4 py-6 md:sticky md:block md:w-[280px] lg:w-[300px]"> <aside className="fixed left-3 md:left-2 lg:left-5 top-15 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto bg-background px-4 py-6 md:sticky md:block md:w-[280px] lg:w-[300px]">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<Link href="/" className="text-4xl font-bold text-primary">{process.env.NEXT_PUBLIC_BLOG_NAME || 'BlogPop'}</Link> <Link href="/" className="text-4xl font-bold text-primary">{process.env.NEXT_PUBLIC_BLOG_NAME || strings.blogPop}</Link>
</div> </div>
<div className="flex flex-1 items-center justify-start space-x-2 md:justify-center"> <div className="flex flex-1 items-center justify-start space-x-2 md:justify-center">
<div className="w-full flex-1 md:w-auto md:flex-none"> <div className="w-full flex-1 md:w-auto md:flex-none">
<Input <Input
placeholder="Search posts..." placeholder={strings.searchPlaceholder}
className="h-9 mb-8 w-full md:w-[250px] lg:w-[270px]" className="h-9 mb-8 w-full md:w-[250px] lg:w-[270px]"
/> />
</div> </div>
@ -86,7 +86,7 @@ export function Sidebar() {
) : error ? ( ) : error ? (
<div className="text-red-500">{error}</div> <div className="text-red-500">{error}</div>
) : posts.length === 0 ? ( ) : posts.length === 0 ? (
<div>No recent posts available.</div> <div>{strings.errorsNoRecentPosts}</div>
) : ( ) : (
<ul className="space-y-2"> <ul className="space-y-2">
{posts {posts
@ -97,7 +97,7 @@ export function Sidebar() {
<Link <Link
href={`/posts/${post.slug}`} href={`/posts/${post.slug}`}
className="text-sm text-muted-foreground hover:text-primary" className="text-sm text-muted-foreground hover:text-primary"
aria-label={`View post titled ${post.title}`} aria-label={`${strings.viewSingular} ${post.title}`}
> >
{post.title} {post.title}
</Link> </Link>
@ -117,7 +117,7 @@ export function Sidebar() {
<div className="animate-spin rounded-full h-16 w-16 border-4 border-t-slate-800 border-white"></div> <div className="animate-spin rounded-full h-16 w-16 border-4 border-t-slate-800 border-white"></div>
</div> </div>
) : uniqueCategories.length === 0 ? ( ) : uniqueCategories.length === 0 ? (
<div>No categories available.</div> <div>{strings.errorsNoCategories}</div>
) : ( ) : (
<ul className="space-y-2"> <ul className="space-y-2">
{uniqueCategories.map((category) => ( {uniqueCategories.map((category) => (
@ -125,7 +125,7 @@ export function Sidebar() {
<Link <Link
href={`/category/${category.slug}`} href={`/category/${category.slug}`}
className="text-sm text-muted-foreground hover:text-primary" className="text-sm text-muted-foreground hover:text-primary"
aria-label={`View posts in category ${category.name}`} aria-label={`${strings.categoriesViewCategoryAriaPrefix} ${category.name} ${strings.categorySingular}`}
> >
{category.name} {category.name}
</Link> </Link>

View File

@ -1,38 +1,38 @@
import Link from "next/link" import Link from "next/link"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
//import strings from "@/strings.json" import strings from "@/strings.json"
export function AdminSidebar() { export function AdminSidebar() {
return ( return (
<aside className="fixed left-3 md:left-2 lg:left-5 top-15 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto bg-background px-4 py-6 md:sticky md:block md:w-[280px] lg:w-[300px]"> <aside className="fixed left-3 md:left-2 lg:left-5 top-15 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto bg-background px-4 py-6 md:sticky md:block md:w-[280px] lg:w-[300px]">
<div className="flex items-center justify-between mb-8"> <div className="flex items-center justify-between mb-8">
<Link href="/admin" className="text-4xl font-bold text-primary">BlogPop</Link> <Link href="/admin" className="text-4xl font-bold text-primary">{strings.blogPop}</Link>
<span className="text-sm text-muted-foreground">v1.0.0</span> <span className="text-sm text-muted-foreground">v1.0.0</span>
</div> </div>
<Card className="mb-6"> <Card className="mb-6">
<CardHeader> <CardHeader>
<CardTitle>Navigation</CardTitle> <CardTitle>{strings.adminNavigationCardTitle}</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ul className="space-y-2"> <ul className="space-y-2">
<li> <li>
<Link href="/admin" className="text-sm text-muted-foreground hover:text-primary"> <Link href="/admin" className="text-sm text-muted-foreground hover:text-primary">
Dashboard {strings.adminSidebarDashboardLinkText}
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/admin/posts" className="text-sm text-muted-foreground hover:text-primary"> <Link href="/admin/posts" className="text-sm text-muted-foreground hover:text-primary">
Posts {strings.adminSidebarPostsLinkText}
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/admin/users" className="text-sm text-muted-foreground hover:text-primary"> <Link href="/admin/users" className="text-sm text-muted-foreground hover:text-primary">
Users {strings.adminSidebarUsersLinkText}
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/admin/logout" className="text-sm text-muted-foreground hover:text-primary"> <Link href="/admin/logout" className="text-sm text-muted-foreground hover:text-primary">
Logout {strings.adminSidebarLogoutLinkText}
</Link> </Link>
</li> </li>
</ul> </ul>

View File

@ -4,6 +4,7 @@ import Link from "next/link"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { X } from "lucide-react" import { X } from "lucide-react"
import { useSidebar } from "@/context/SidebarContext" import { useSidebar } from "@/context/SidebarContext"
import strings from "@/strings.json"
export function MobileAdminSidebar() { export function MobileAdminSidebar() {
const { isOpen, toggleSidebar } = useSidebar() const { isOpen, toggleSidebar } = useSidebar()
@ -16,25 +17,25 @@ export function MobileAdminSidebar() {
> >
<div className="flex items-center justify-between mb-6 mt-12"> <div className="flex items-center justify-between mb-6 mt-12">
<Link href="/" className="text-4xl font-bold text-primary"> <Link href="/" className="text-4xl font-bold text-primary">
{process.env.BLOG_NAME || "BlogPop"} {process.env.BLOG_NAME || strings.blogPop}
</Link> </Link>
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden"> <Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden">
<X className="h-6 w-6" /> <X className="h-6 w-6" />
<span className="sr-only">Close sidebar</span> <span className="sr-only">{strings.screenReaderCloseSidebar}</span>
</Button> </Button>
</div> </div>
<nav className="flex flex-col space-y-4"> <nav className="flex flex-col space-y-4">
<Link href="/admin" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/admin" className="text-lg text-muted-foreground hover:text-primary">
Dashboard {strings.adminSidebarDashboardLinkText}
</Link> </Link>
<Link href="/admin/posts" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/admin/posts" className="text-lg text-muted-foreground hover:text-primary">
Posts {strings.adminSidebarPostsLinkText}
</Link> </Link>
<Link href="/admin/users" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/admin/users" className="text-lg text-muted-foreground hover:text-primary">
Users {strings.adminSidebarUsersLinkText}
</Link> </Link>
<Link href="/admin/logout" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/admin/logout" className="text-lg text-muted-foreground hover:text-primary">
Logout {strings.adminSidebarLogoutLinkText}
</Link> </Link>
</nav> </nav>
</aside> </aside>

View File

@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button"
import { X } from "lucide-react" import { X } from "lucide-react"
import { useSidebar } from "@/context/SidebarContext" import { useSidebar } from "@/context/SidebarContext"
import config from "@/config.json" import config from "@/config.json"
import strings from "@/strings.json"
export function MobileSidebar() { export function MobileSidebar() {
const { isOpen, toggleSidebar } = useSidebar() const { isOpen, toggleSidebar } = useSidebar()
@ -17,22 +18,19 @@ export function MobileSidebar() {
> >
<div className="flex items-center justify-between mb-6 mt-12"> <div className="flex items-center justify-between mb-6 mt-12">
<Link href="/" className="text-4xl font-bold text-primary"> <Link href="/" className="text-4xl font-bold text-primary">
{process.env.BLOG_NAME || "BlogPop"} {process.env.BLOG_NAME || strings.blogPop}
</Link> </Link>
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden"> <Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden">
<X className="h-6 w-6" /> <X className="h-6 w-6" />
<span className="sr-only">Close sidebar</span> <span className="sr-only">{strings.screenReaderCloseSidebar}</span>
</Button> </Button>
</div> </div>
<nav className="flex flex-col space-y-4"> <nav className="flex flex-col space-y-4">
<Link href="/" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/" className="text-lg text-muted-foreground hover:text-primary">
Home {strings.homeLinkTextNavbar}
</Link> </Link>
<Link href="/categories" className="text-lg text-muted-foreground hover:text-primary"> <Link href="/categories" className="text-lg text-muted-foreground hover:text-primary">
Categories {strings.categoriesLinkTextNavbar}
</Link>
<Link href="/contact" className="text-lg text-muted-foreground hover:text-primary">
Contact
</Link> </Link>
{config.personalWebsite && ( {config.personalWebsite && (
<Link href={config.personalWebsiteUrl} className="text-lg text-muted-foreground hover:text-primary"> <Link href={config.personalWebsiteUrl} className="text-lg text-muted-foreground hover:text-primary">

View File

@ -9,10 +9,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-dropdown-menu": "^2.1.5",
"@radix-ui/react-label": "^2.1.1", "@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-select": "^2.1.4", "@radix-ui/react-select": "^2.1.5",
"@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-visually-hidden": "^1.1.1", "@radix-ui/react-visually-hidden": "^1.1.1",
"@uiw/react-md-editor": "^4.0.5", "@uiw/react-md-editor": "^4.0.5",
@ -21,7 +21,7 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"geist": "^1.3.1", "geist": "^1.3.1",
"is-mobile": "^5.0.0", "is-mobile": "^5.0.0",
"lucide-react": "^0.471.1", "lucide-react": "^0.471.2",
"next": "15.1.4", "next": "15.1.4",
"next-themes": "^0.4.4", "next-themes": "^0.4.4",
"react": "^19.0.0", "react": "^19.0.0",
@ -34,12 +34,12 @@
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.7.3", "typescript": "^5.7.3",
"@types/node": "^20.17.13", "@types/node": "^20.17.16",
"@types/react": "^19.0.7", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"eslint": "^9.18.0", "eslint": "^9.19.0",
"eslint-config-next": "15.1.4", "eslint-config-next": "15.1.4",
"@eslint/eslintrc": "^3.2.0" "@eslint/eslintrc": "^3.2.0"
} }

View File

@ -1,10 +1,18 @@
{ {
"blogPop": "BlogPop",
"latestPostsHeader": "Latest Posts", "latestPostsHeader": "Latest Posts",
"categoriesHeader": "Categories", "categoriesHeader": "Categories",
"adminHeader": "Admin", "adminHeader": "Admin",
"adminNewPostHeader": "New Post", "adminNewPostHeader": "New Post",
"adminNewUserHeader": "New User", "adminNewUserHeader": "New User",
"usersHeader": "Users", "usersHeader": "Users",
"postsHeader": "Posts",
"searchPlaceholder": "Search",
"screenReaderToggleSidebar": "Toggle sidebar",
"screenReaderCloseSidebar": "Close sidebar",
"homeLinkTextNavbar": "Home", "homeLinkTextNavbar": "Home",
"categoriesLinkTextNavbar": "Categories", "categoriesLinkTextNavbar": "Categories",
@ -14,7 +22,10 @@
"newPostCardTitle": "Create New Post", "newPostCardTitle": "Create New Post",
"newUserCardTitle": "Create User", "newUserCardTitle": "Create User",
"totalUsersCardTitle": "Total Users", "totalUsersCardTitle": "Total Users",
"adminQuickActionsCardTitle": "Quick Actions",
"totalUsersLoggedInMonthCardTitle": "Users Logged In", "totalUsersLoggedInMonthCardTitle": "Users Logged In",
"adminNavigationCardTitle": "Navigation",
"adminLoginCardTitle": "Administration Panel",
"newPostTitleFieldLabel": "Title", "newPostTitleFieldLabel": "Title",
"newPostDescriptionFieldLabel": "Description", "newPostDescriptionFieldLabel": "Description",
@ -22,28 +33,34 @@
"newPostSlugFieldLabel": "Slug", "newPostSlugFieldLabel": "Slug",
"newPostContentFieldLabel": "Post Content", "newPostContentFieldLabel": "Post Content",
"newUserNameFieldLabel": "Name",
"newUserEmailFieldLabel": "Email",
"newUserPasswordFieldLabel": "Password",
"newPostTitleFieldPlaceholder": "Enter post title", "newPostTitleFieldPlaceholder": "Enter post title",
"newPostDescriptionFieldPlaceholder": "Enter post description", "newPostDescriptionFieldPlaceholder": "Enter post description",
"newPostCategoryFieldPlaceholder": "Select a category", "newPostCategoryFieldPlaceholder": "Select a category",
"newPostSlugFieldPlaceholder": "Enter post slug (i.e. /example-slug)", "newPostSlugFieldPlaceholder": "Enter post slug (i.e. /example-slug)",
"newUserNameFieldLabel": "Name",
"newUserEmailFieldLabel": "Email",
"newUserPasswordFieldLabel": "Password",
"newUserNameFieldPlaceholder": "Enter desired username", "newUserNameFieldPlaceholder": "Enter desired username",
"newUserEmailFieldPlaceholder": "Enter user email", "newUserEmailFieldPlaceholder": "Enter user email",
"newUserPasswordFieldPlaceholder": "Enter user password", "newUserPasswordFieldPlaceholder": "Enter user password",
"categoriesCardDescriptionPre": "Explore posts from", "adminLoginUsernameFieldLabel": "Username",
"adminLoginPasswordFieldLabel": "Password",
"totalUsersLoggedInMonthCardDescription": "This month", "totalUsersLoggedInMonthCardDescription": "This month",
"adminLoginCardDescription": "Please authenticate with your credentials.",
"categoriesCardDescriptionPre": "Explore posts from",
"categoriesLastUpdatedLabel": "Last updated", "categoriesLastUpdatedLabel": "Last updated",
"categoriesViewPostsFromLinkText": "View posts", "categoriesViewPostsFromLinkText": "View posts",
"recentPostsReadMoreFromLinkText": "Read more", "recentPostsReadMoreFromLinkText": "Read more",
"categoriesViewCategoryAriaPrefix": "View the",
"categoriesPostUnitSingle": "post", "categoriesPostUnitSingle": "post",
"categoriesPostUnitPlural": "posts", "categoriesPostUnitPlural": "posts",
@ -53,5 +70,86 @@
"createPostButtonText": "Create Post", "createPostButtonText": "Create Post",
"createUserButtonText": "Create User", "createUserButtonText": "Create User",
"recentPostsPublishedOnLabel": "Published " "adminLoginButtonText": "Login",
"viewSingular": "View",
"categorySingular": "Category",
"recentPostsPublishedOnLabel": "Published ",
"blankPostTitlePlaceholder": "Untitled Post",
"blankPostDatePlaceholder": "an unknown date",
"postPublishedOnLabel": "Published on",
"adminSidebarDashboardLinkText": "Dashboard",
"adminSidebarPostsLinkText": "Posts",
"adminSidebarUsersLinkText": "Users",
"adminSidebarLogoutLinkText": "Logout",
"logsDetectedLoginPage": "[i] Detected login page, skipping validation",
"logsFoundKey": "[i] Key found in browser cookies, checking validity...",
"logsCalculatingPostCt": "[i] Calculating post count...",
"logsCalculatingUserCt": "[i] Calculating user count...",
"logsFetchPostList": "[i] Fetching post list...",
"logsFetchCategoryList": "[i] Fetching category list...",
"logsOnSlugNavigate": "[i] Navigated to slug: ",
"logsOnFoundCategory": "[✓] Found category:",
"logsOnFetchedCategory": "[✓] Fetched categories",
"logsOnFetchedPosts": "[✓] Fetched posts",
"logsTotalPosts": "[✓] Total posts:",
"logsTotalUsers": "[✓] Total users:",
"logsKeyIsValid": "[✓] Key is valid",
"logsAdminLoginKeyIsValid": "[✓] Key is valid, redirecting to admin panel",
"logsAdminLoginSuccessful": "[✓] Login successful, redirecting to admin panel",
"successSuperGeneric": "Success",
"errorsSuperGeneric": "Error",
"errorsUnknownError": "An unknown error occurred.",
"errorsUnknownErrorLong": "An unknown error occurred. Please try again later.",
"errorsFetchPostListFailCtx": "Failed to fetch post list:",
"errorsFetchCategoryListFailCtx": "Failed to fetch category list:",
"errorsFetchTotalPostCtErrFancy": "[!] Failed to fetch total post count:",
"errorsFetchTotalUserCtErrFancy": "[!] Failed to fetch total user count:",
"errorsFetchPostsFailFancy": "[!] Failed to fetch posts",
"errorsLoginFailFancy": "[!] Failed to login",
"errorsFailedKeyCheckFancy": "[!] Failed to check key, skipping validation and clearing cookie",
"errorsFetchTotalPostCtErr": "Failed to fetch total post count",
"errorsFetchTotalUserCtErr": "Failed to fetch total user count",
"errorsConnectionFail": "Failed to connect to the server. Please try again later.",
"errorsFetchCategoryListFail": "Failed to fetch category list",
"errorsFetchDataFail": "Failed to fetch data",
"errorsCreateUserFail": "Failed to create user",
"errorsCreatePostFail": "Failed to create post",
"errorsFetchPostListErr": "Failed to fetch post list. Please try again later.",
"errorsFetchPostListErrCtx": "Error fetching post list:",
"errorsFetchPostListErrCtxFancy": "[!] Error fetching post list:",
"errorsFetchDataErrCtxFancy": "[!] Error fetching data:",
"errorsFetchCategoriesErrCtxFancy": "[!] Error fetching categories:",
"errorsFetchPostErr": "Error fetching post",
"errorsFetchPostErrMd": "# Error\n\nCould not load the post.",
"errorsNoKeyFoundFancy": "[!] No key found, clearing cookies and redirecting to login",
"errorsKeyInvalidFancy": "[✖] Key is invalid, clearing cookie and redirecting to login",
"errorsFetchCategoryNotFound": "Could not find requested category",
"errorsNoRecentPosts": "No recent posts available.",
"errorsNoCategories": "No categories available."
} }