migrate all strings to strings.json, reorganize strings.json, bump packages, add todo, rm unnecessary import, minor error fixes
This commit is contained in:
parent
05f9be8493
commit
d52330d191
@ -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`
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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 ? (
|
||||
<div className="flex items-center text-red-500">
|
||||
<CircleAlert />
|
||||
<p className="text-base ml-1.5">Error</p>
|
||||
<p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
|
||||
</div>
|
||||
) : (
|
||||
totalUsers
|
||||
@ -149,7 +149,7 @@ export default function Home() {
|
||||
) : postCardError ? (
|
||||
<div className="flex items-center text-red-500">
|
||||
<CircleAlert />
|
||||
<p className="text-base ml-1.5">Error</p>
|
||||
<p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
|
||||
</div>
|
||||
) : (
|
||||
totalPosts
|
||||
@ -162,19 +162,19 @@ export default function Home() {
|
||||
<div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<Card className="flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Actions</CardTitle>
|
||||
<CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<Button className="w-full justify-start" variant="outline" asChild>
|
||||
<a href="/admin/posts/new">
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
New Post
|
||||
{strings.createPostButtonText}
|
||||
</a>
|
||||
</Button>
|
||||
<Button className="w-full justify-start" variant="outline" asChild>
|
||||
<a href="/admin/users/new">
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
New User
|
||||
{strings.createUserButtonText}
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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 (
|
||||
<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">
|
||||
<Card className="flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Actions</CardTitle>
|
||||
<CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<Button className="w-full justify-start" variant="outline" asChild>
|
||||
<a href="/admin/post">
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
New Post
|
||||
{strings.createPostButtonText}
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -90,7 +90,7 @@ export default function Posts() {
|
||||
) : postCardError ? (
|
||||
<div className="flex items-center text-red-500">
|
||||
<CircleAlert />
|
||||
<p className="text-base ml-1.5">Error</p>
|
||||
<p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
|
||||
</div>
|
||||
) : (
|
||||
totalPosts
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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() {
|
||||
<div className="grid gap-6 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<Card className="flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Actions</CardTitle>
|
||||
<CardTitle>{strings.adminQuickActionsCardTitle}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<Button className="w-full justify-start" variant="outline" asChild>
|
||||
<a href="/admin/users/new">
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
New User
|
||||
{strings.createUserButtonText}
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
@ -90,7 +90,7 @@ export default function Users() {
|
||||
) : userCtCardError ? (
|
||||
<div className="flex items-center text-red-500">
|
||||
<CircleAlert />
|
||||
<p className="text-base ml-1.5">Error</p>
|
||||
<p className="text-base ml-1.5">{strings.errorsSuperGeneric}</p>
|
||||
</div>
|
||||
) : (
|
||||
totalUsers
|
||||
|
@ -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)
|
||||
})
|
||||
}, [])
|
||||
|
@ -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()) {
|
||||
|
10
app/page.tsx
10
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);
|
||||
}
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<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">
|
||||
<span className="border border-white text-bold px-3 py-1 rounded-md">
|
||||
<Tag className="w-4 h-4 inline-block mr-1" /> {postCategory}
|
||||
|
@ -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<string, string>);
|
||||
|
||||
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({
|
||||
<div className={cn("relative flex min-h-screen flex-col", className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Administration Panel</CardTitle>
|
||||
<CardTitle className="text-2xl">{strings.adminLoginCardTitle}</CardTitle>
|
||||
<CardDescription>
|
||||
Please authenticate with your credentials.
|
||||
{strings.adminLoginCardDescription}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -122,7 +117,7 @@ export function LoginForm({
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Label htmlFor="username">{strings.adminLoginUsernameFieldLabel}</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
@ -133,7 +128,7 @@ export function LoginForm({
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Label htmlFor="password">{strings.adminLoginPasswordFieldLabel}</Label>
|
||||
</div>
|
||||
<Input
|
||||
id="password"
|
||||
@ -144,7 +139,7 @@ export function LoginForm({
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Login
|
||||
{strings.adminLoginButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -48,11 +48,11 @@ export function Navbar() {
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="ghost" size="icon" className="md:hidden lg:hidden" onClick={toggleSidebar}>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle sidebar</span>
|
||||
<span className="sr-only">{strings.screenReaderToggleSidebar}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<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>
|
||||
|
@ -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 (
|
||||
<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">
|
||||
<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 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">
|
||||
<Input
|
||||
placeholder="Search posts..."
|
||||
placeholder={strings.searchPlaceholder}
|
||||
className="h-9 mb-8 w-full md:w-[250px] lg:w-[270px]"
|
||||
/>
|
||||
</div>
|
||||
@ -86,7 +86,7 @@ export function Sidebar() {
|
||||
) : error ? (
|
||||
<div className="text-red-500">{error}</div>
|
||||
) : posts.length === 0 ? (
|
||||
<div>No recent posts available.</div>
|
||||
<div>{strings.errorsNoRecentPosts}</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{posts
|
||||
@ -97,7 +97,7 @@ export function Sidebar() {
|
||||
<Link
|
||||
href={`/posts/${post.slug}`}
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
aria-label={`View post titled ${post.title}`}
|
||||
aria-label={`${strings.viewSingular} ${post.title}`}
|
||||
>
|
||||
{post.title}
|
||||
</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>
|
||||
) : uniqueCategories.length === 0 ? (
|
||||
<div>No categories available.</div>
|
||||
<div>{strings.errorsNoCategories}</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{uniqueCategories.map((category) => (
|
||||
@ -125,7 +125,7 @@ export function Sidebar() {
|
||||
<Link
|
||||
href={`/category/${category.slug}`}
|
||||
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}
|
||||
</Link>
|
||||
|
@ -1,38 +1,38 @@
|
||||
import Link from "next/link"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
//import strings from "@/strings.json"
|
||||
import strings from "@/strings.json"
|
||||
|
||||
export function AdminSidebar() {
|
||||
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]">
|
||||
<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>
|
||||
</div>
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Navigation</CardTitle>
|
||||
<CardTitle>{strings.adminNavigationCardTitle}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<Link href="/admin" className="text-sm text-muted-foreground hover:text-primary">
|
||||
Dashboard
|
||||
{strings.adminSidebarDashboardLinkText}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/posts" className="text-sm text-muted-foreground hover:text-primary">
|
||||
Posts
|
||||
{strings.adminSidebarPostsLinkText}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/users" className="text-sm text-muted-foreground hover:text-primary">
|
||||
Users
|
||||
{strings.adminSidebarUsersLinkText}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/admin/logout" className="text-sm text-muted-foreground hover:text-primary">
|
||||
Logout
|
||||
{strings.adminSidebarLogoutLinkText}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -4,6 +4,7 @@ import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { X } from "lucide-react"
|
||||
import { useSidebar } from "@/context/SidebarContext"
|
||||
import strings from "@/strings.json"
|
||||
|
||||
export function MobileAdminSidebar() {
|
||||
const { isOpen, toggleSidebar } = useSidebar()
|
||||
@ -16,25 +17,25 @@ export function MobileAdminSidebar() {
|
||||
>
|
||||
<div className="flex items-center justify-between mb-6 mt-12">
|
||||
<Link href="/" className="text-4xl font-bold text-primary">
|
||||
{process.env.BLOG_NAME || "BlogPop"}
|
||||
{process.env.BLOG_NAME || strings.blogPop}
|
||||
</Link>
|
||||
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden">
|
||||
<X className="h-6 w-6" />
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<span className="sr-only">{strings.screenReaderCloseSidebar}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<nav className="flex flex-col space-y-4">
|
||||
<Link href="/admin" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Dashboard
|
||||
{strings.adminSidebarDashboardLinkText}
|
||||
</Link>
|
||||
<Link href="/admin/posts" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Posts
|
||||
{strings.adminSidebarPostsLinkText}
|
||||
</Link>
|
||||
<Link href="/admin/users" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Users
|
||||
{strings.adminSidebarUsersLinkText}
|
||||
</Link>
|
||||
<Link href="/admin/logout" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Logout
|
||||
{strings.adminSidebarLogoutLinkText}
|
||||
</Link>
|
||||
</nav>
|
||||
</aside>
|
||||
|
@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { X } from "lucide-react"
|
||||
import { useSidebar } from "@/context/SidebarContext"
|
||||
import config from "@/config.json"
|
||||
import strings from "@/strings.json"
|
||||
|
||||
export function MobileSidebar() {
|
||||
const { isOpen, toggleSidebar } = useSidebar()
|
||||
@ -17,22 +18,19 @@ export function MobileSidebar() {
|
||||
>
|
||||
<div className="flex items-center justify-between mb-6 mt-12">
|
||||
<Link href="/" className="text-4xl font-bold text-primary">
|
||||
{process.env.BLOG_NAME || "BlogPop"}
|
||||
{process.env.BLOG_NAME || strings.blogPop}
|
||||
</Link>
|
||||
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="md:hidden">
|
||||
<X className="h-6 w-6" />
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<span className="sr-only">{strings.screenReaderCloseSidebar}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<nav className="flex flex-col space-y-4">
|
||||
<Link href="/" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Home
|
||||
{strings.homeLinkTextNavbar}
|
||||
</Link>
|
||||
<Link href="/categories" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Categories
|
||||
</Link>
|
||||
<Link href="/contact" className="text-lg text-muted-foreground hover:text-primary">
|
||||
Contact
|
||||
{strings.categoriesLinkTextNavbar}
|
||||
</Link>
|
||||
{config.personalWebsite && (
|
||||
<Link href={config.personalWebsiteUrl} className="text-lg text-muted-foreground hover:text-primary">
|
||||
|
14
package.json
14
package.json
@ -9,10 +9,10 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||
"@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-visually-hidden": "^1.1.1",
|
||||
"@uiw/react-md-editor": "^4.0.5",
|
||||
@ -21,7 +21,7 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"geist": "^1.3.1",
|
||||
"is-mobile": "^5.0.0",
|
||||
"lucide-react": "^0.471.1",
|
||||
"lucide-react": "^0.471.2",
|
||||
"next": "15.1.4",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
@ -34,12 +34,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3",
|
||||
"@types/node": "^20.17.13",
|
||||
"@types/react": "^19.0.7",
|
||||
"@types/node": "^20.17.16",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"postcss": "^8.5.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-config-next": "15.1.4",
|
||||
"@eslint/eslintrc": "^3.2.0"
|
||||
}
|
||||
|
110
strings.json
110
strings.json
@ -1,10 +1,18 @@
|
||||
{
|
||||
"blogPop": "BlogPop",
|
||||
|
||||
"latestPostsHeader": "Latest Posts",
|
||||
"categoriesHeader": "Categories",
|
||||
"adminHeader": "Admin",
|
||||
"adminNewPostHeader": "New Post",
|
||||
"adminNewUserHeader": "New User",
|
||||
"usersHeader": "Users",
|
||||
"postsHeader": "Posts",
|
||||
|
||||
"searchPlaceholder": "Search",
|
||||
|
||||
"screenReaderToggleSidebar": "Toggle sidebar",
|
||||
"screenReaderCloseSidebar": "Close sidebar",
|
||||
|
||||
"homeLinkTextNavbar": "Home",
|
||||
"categoriesLinkTextNavbar": "Categories",
|
||||
@ -14,7 +22,10 @@
|
||||
"newPostCardTitle": "Create New Post",
|
||||
"newUserCardTitle": "Create User",
|
||||
"totalUsersCardTitle": "Total Users",
|
||||
"adminQuickActionsCardTitle": "Quick Actions",
|
||||
"totalUsersLoggedInMonthCardTitle": "Users Logged In",
|
||||
"adminNavigationCardTitle": "Navigation",
|
||||
"adminLoginCardTitle": "Administration Panel",
|
||||
|
||||
"newPostTitleFieldLabel": "Title",
|
||||
"newPostDescriptionFieldLabel": "Description",
|
||||
@ -22,28 +33,34 @@
|
||||
"newPostSlugFieldLabel": "Slug",
|
||||
"newPostContentFieldLabel": "Post Content",
|
||||
|
||||
"newUserNameFieldLabel": "Name",
|
||||
"newUserEmailFieldLabel": "Email",
|
||||
"newUserPasswordFieldLabel": "Password",
|
||||
|
||||
"newPostTitleFieldPlaceholder": "Enter post title",
|
||||
"newPostDescriptionFieldPlaceholder": "Enter post description",
|
||||
"newPostCategoryFieldPlaceholder": "Select a category",
|
||||
"newPostSlugFieldPlaceholder": "Enter post slug (i.e. /example-slug)",
|
||||
|
||||
"newUserNameFieldLabel": "Name",
|
||||
"newUserEmailFieldLabel": "Email",
|
||||
"newUserPasswordFieldLabel": "Password",
|
||||
|
||||
"newUserNameFieldPlaceholder": "Enter desired username",
|
||||
"newUserEmailFieldPlaceholder": "Enter user email",
|
||||
"newUserPasswordFieldPlaceholder": "Enter user password",
|
||||
|
||||
"categoriesCardDescriptionPre": "Explore posts from",
|
||||
"adminLoginUsernameFieldLabel": "Username",
|
||||
"adminLoginPasswordFieldLabel": "Password",
|
||||
|
||||
"totalUsersLoggedInMonthCardDescription": "This month",
|
||||
"adminLoginCardDescription": "Please authenticate with your credentials.",
|
||||
|
||||
"categoriesCardDescriptionPre": "Explore posts from",
|
||||
|
||||
"categoriesLastUpdatedLabel": "Last updated",
|
||||
|
||||
"categoriesViewPostsFromLinkText": "View posts",
|
||||
"recentPostsReadMoreFromLinkText": "Read more",
|
||||
|
||||
"categoriesViewCategoryAriaPrefix": "View the",
|
||||
|
||||
"categoriesPostUnitSingle": "post",
|
||||
"categoriesPostUnitPlural": "posts",
|
||||
|
||||
@ -53,5 +70,86 @@
|
||||
"createPostButtonText": "Create Post",
|
||||
"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."
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user