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`
|
- [ ] 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`
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -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()) {
|
||||||
|
10
app/page.tsx
10
app/page.tsx
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
14
package.json
14
package.json
@ -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"
|
||||||
}
|
}
|
||||||
|
110
strings.json
110
strings.json
@ -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."
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user