@jhits/plugin-blog 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/categories.d.ts.map +1 -1
- package/dist/api/categories.js +43 -12
- package/dist/api/handler.d.ts +1 -0
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +259 -32
- package/dist/hooks/useBlogs.d.ts +2 -0
- package/dist/hooks/useBlogs.d.ts.map +1 -1
- package/dist/hooks/useBlogs.js +10 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/i18n.d.ts +14 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +58 -0
- package/dist/lib/mappers/apiMapper.d.ts +18 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
- package/dist/lib/mappers/apiMapper.js +1 -0
- package/dist/state/reducer.d.ts.map +1 -1
- package/dist/state/reducer.js +11 -6
- package/dist/state/types.d.ts +5 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/state/types.js +1 -0
- package/dist/types/post.d.ts +25 -0
- package/dist/types/post.d.ts.map +1 -1
- package/dist/utils/client.d.ts +2 -0
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +3 -1
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +130 -4
- package/dist/views/CanvasEditor/EditorHeader.d.ts +5 -1
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +23 -5
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.js +14 -4
- package/dist/views/PostManager/LanguageFlags.d.ts +11 -0
- package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -0
- package/dist/views/PostManager/LanguageFlags.js +60 -0
- package/dist/views/PostManager/PostCards.d.ts.map +1 -1
- package/dist/views/PostManager/PostCards.js +4 -1
- package/dist/views/PostManager/PostFilters.d.ts +4 -1
- package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
- package/dist/views/PostManager/PostFilters.js +13 -3
- package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
- package/dist/views/PostManager/PostManagerView.js +24 -3
- package/dist/views/PostManager/PostTable.d.ts.map +1 -1
- package/dist/views/PostManager/PostTable.js +4 -1
- package/package.json +3 -3
- package/src/api/categories.ts +58 -11
- package/src/api/handler.ts +286 -31
- package/src/hooks/useBlogs.ts +12 -1
- package/src/index.tsx +7 -3
- package/src/lib/i18n.ts +78 -0
- package/src/lib/mappers/apiMapper.ts +20 -0
- package/src/state/reducer.ts +12 -6
- package/src/state/types.ts +5 -0
- package/src/types/post.ts +28 -0
- package/src/utils/client.ts +4 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +164 -20
- package/src/views/CanvasEditor/EditorHeader.tsx +93 -18
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +15 -4
- package/src/views/PostManager/LanguageFlags.tsx +136 -0
- package/src/views/PostManager/PostCards.tsx +22 -12
- package/src/views/PostManager/PostFilters.tsx +38 -1
- package/src/views/PostManager/PostManagerView.tsx +25 -2
- package/src/views/PostManager/PostTable.tsx +12 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Flags Component
|
|
3
|
+
* Displays interactive language flags with custom tooltips
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
9
|
+
import { createPortal } from 'react-dom';
|
|
10
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
11
|
+
import { PostListItem } from '../../types/post';
|
|
12
|
+
|
|
13
|
+
interface LanguageFlagsProps {
|
|
14
|
+
post: PostListItem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LANGUAGE_COUNTRY_CODES: Record<string, string> = {
|
|
18
|
+
en: 'gb',
|
|
19
|
+
nl: 'nl',
|
|
20
|
+
sv: 'se',
|
|
21
|
+
de: 'de',
|
|
22
|
+
fr: 'fr',
|
|
23
|
+
es: 'es',
|
|
24
|
+
it: 'it',
|
|
25
|
+
pt: 'pt',
|
|
26
|
+
pl: 'pl',
|
|
27
|
+
ru: 'ru',
|
|
28
|
+
ja: 'jp',
|
|
29
|
+
zh: 'cn',
|
|
30
|
+
ar: 'sa',
|
|
31
|
+
tr: 'tr',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getFlagUrl = (lang: string) => {
|
|
35
|
+
const countryCode = LANGUAGE_COUNTRY_CODES[lang] || lang;
|
|
36
|
+
return `https://flagcdn.com/w40/${countryCode}.png`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function LanguageFlags({ post }: LanguageFlagsProps) {
|
|
40
|
+
const [hoveredLang, setHoveredLang] = useState<string | null>(null);
|
|
41
|
+
const [tooltipCoords, setTooltipCoords] = useState<{ top: number; left: number } | null>(null);
|
|
42
|
+
const flagRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (hoveredLang && flagRefs.current[hoveredLang]) {
|
|
46
|
+
const rect = flagRefs.current[hoveredLang]!.getBoundingClientRect();
|
|
47
|
+
setTooltipCoords({
|
|
48
|
+
top: rect.top, // Viewport-relative
|
|
49
|
+
left: rect.left + rect.width / 2, // Center horizontally
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, [hoveredLang]);
|
|
53
|
+
|
|
54
|
+
if (!post.availableLanguages || post.availableLanguages.length === 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex gap-2">
|
|
60
|
+
{post.availableLanguages.map((lang) => {
|
|
61
|
+
const langData = post.languages?.[lang];
|
|
62
|
+
const status = langData?.status || 'draft';
|
|
63
|
+
const langTitle = langData?.metadata?.title || post.title;
|
|
64
|
+
const isHovered = hoveredLang === lang;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
key={lang}
|
|
69
|
+
ref={(el) => { flagRefs.current[lang] = el; }}
|
|
70
|
+
className="relative"
|
|
71
|
+
onMouseEnter={() => setHoveredLang(lang)}
|
|
72
|
+
onMouseLeave={() => setHoveredLang(null)}
|
|
73
|
+
>
|
|
74
|
+
<motion.div
|
|
75
|
+
whileHover={{ scale: 1.15 }}
|
|
76
|
+
className={`relative flex items-center justify-center w-7 h-4.5 rounded shadow-sm overflow-hidden border cursor-help transition-colors ${
|
|
77
|
+
status === 'published'
|
|
78
|
+
? 'border-green-500/40 bg-white'
|
|
79
|
+
: 'border-amber-500/40 bg-white opacity-90'
|
|
80
|
+
}`}
|
|
81
|
+
>
|
|
82
|
+
<img
|
|
83
|
+
src={getFlagUrl(lang)}
|
|
84
|
+
alt={lang}
|
|
85
|
+
className="w-full h-full object-cover"
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{status !== 'published' && (
|
|
89
|
+
<div className="absolute inset-0 bg-amber-500/5" />
|
|
90
|
+
)}
|
|
91
|
+
</motion.div>
|
|
92
|
+
|
|
93
|
+
{typeof document !== 'undefined' && createPortal(
|
|
94
|
+
<AnimatePresence>
|
|
95
|
+
{isHovered && tooltipCoords && (
|
|
96
|
+
<motion.div
|
|
97
|
+
initial={{ opacity: 0, y: -90, x: '-50%', scale: 0.95 }}
|
|
98
|
+
animate={{ opacity: 1, y: -100, x: '-50%', scale: 1 }}
|
|
99
|
+
exit={{ opacity: 0, y: -90, x: '-50%', scale: 0.95 }}
|
|
100
|
+
className="fixed z-[9999] pointer-events-none"
|
|
101
|
+
style={{
|
|
102
|
+
top: tooltipCoords.top - 12,
|
|
103
|
+
left: tooltipCoords.left,
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<div className="bg-neutral-900 dark:bg-neutral-800 text-white p-3 rounded-xl shadow-2xl border border-neutral-700/50 backdrop-blur-md min-w-[180px] max-w-[280px]">
|
|
107
|
+
<div className="flex items-center justify-between gap-4 mb-1.5">
|
|
108
|
+
<span className="text-[10px] font-black uppercase tracking-widest text-neutral-400">
|
|
109
|
+
{lang.toUpperCase()} Version
|
|
110
|
+
</span>
|
|
111
|
+
<span className={`text-[9px] font-black uppercase px-1.5 py-0.5 rounded ${
|
|
112
|
+
status === 'published'
|
|
113
|
+
? 'bg-green-500/20 text-green-400'
|
|
114
|
+
: 'bg-amber-500/20 text-amber-400'
|
|
115
|
+
}`}>
|
|
116
|
+
{status}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
<p className="text-xs font-bold leading-snug line-clamp-2">
|
|
120
|
+
{langTitle}
|
|
121
|
+
</p>
|
|
122
|
+
|
|
123
|
+
{/* Tooltip Arrow */}
|
|
124
|
+
<div className="absolute top-full left-1/2 -translate-x-1/2 border-8 border-transparent border-t-neutral-900 dark:border-t-neutral-800" />
|
|
125
|
+
</div>
|
|
126
|
+
</motion.div>
|
|
127
|
+
)}
|
|
128
|
+
</AnimatePresence>,
|
|
129
|
+
document.body
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -10,6 +10,7 @@ import { Calendar, User, UserCheck } from 'lucide-react';
|
|
|
10
10
|
import { Image } from '@jhits/plugin-images';
|
|
11
11
|
import { PostListItem, PostStatus } from '../../types/post';
|
|
12
12
|
import { PostActionsMenu } from './PostActionsMenu';
|
|
13
|
+
import { LanguageFlags } from './LanguageFlags';
|
|
13
14
|
import { useSession } from 'next-auth/react';
|
|
14
15
|
|
|
15
16
|
export interface PostCardsProps {
|
|
@@ -21,7 +22,7 @@ export interface PostCardsProps {
|
|
|
21
22
|
onDelete: (postId: string) => void;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function getStatusBadgeColor(status: PostStatus) {
|
|
25
|
+
function getStatusBadgeColor(status: PostStatus | 'not-translated') {
|
|
25
26
|
switch (status) {
|
|
26
27
|
case 'published':
|
|
27
28
|
return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
|
|
@@ -31,6 +32,8 @@ function getStatusBadgeColor(status: PostStatus) {
|
|
|
31
32
|
return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
|
|
32
33
|
case 'archived':
|
|
33
34
|
return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
|
|
35
|
+
case 'not-translated':
|
|
36
|
+
return 'bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/20 italic';
|
|
34
37
|
default:
|
|
35
38
|
return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
|
|
36
39
|
}
|
|
@@ -98,10 +101,10 @@ export function PostCards({
|
|
|
98
101
|
{posts.map((post) => (
|
|
99
102
|
<div
|
|
100
103
|
key={post.id}
|
|
101
|
-
className="bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group"
|
|
104
|
+
className="flex flex-col bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group h-full"
|
|
102
105
|
>
|
|
103
106
|
{/* Featured Image */}
|
|
104
|
-
<div className="relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden">
|
|
107
|
+
<div className="relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden flex-shrink-0">
|
|
105
108
|
{post.featuredImage ? (
|
|
106
109
|
<Image
|
|
107
110
|
id={post.featuredImage}
|
|
@@ -139,9 +142,9 @@ export function PostCards({
|
|
|
139
142
|
</div>
|
|
140
143
|
|
|
141
144
|
{/* Card Content */}
|
|
142
|
-
<div className="p-6">
|
|
145
|
+
<div className="p-6 flex flex-col flex-1">
|
|
143
146
|
{/* Title & Slug */}
|
|
144
|
-
<div className="mb-4">
|
|
147
|
+
<div className="mb-4 min-h-[72px]">
|
|
145
148
|
<button
|
|
146
149
|
onClick={() => onEdit(post.id)}
|
|
147
150
|
className="text-left w-full hover:cursor-pointer"
|
|
@@ -150,20 +153,27 @@ export function PostCards({
|
|
|
150
153
|
{post.title}
|
|
151
154
|
</h3>
|
|
152
155
|
</button>
|
|
153
|
-
<p className="text-xs text-neutral-500 dark:text-neutral-400 font-mono">
|
|
156
|
+
<p className="text-xs text-neutral-500 dark:text-neutral-400 font-mono line-clamp-1">
|
|
154
157
|
/{post.slug}
|
|
155
158
|
</p>
|
|
156
159
|
</div>
|
|
157
160
|
|
|
158
161
|
{/* Excerpt */}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
<div className="flex-1 mb-4 min-h-[40px]">
|
|
163
|
+
{post.excerpt && (
|
|
164
|
+
<p className="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-2">
|
|
165
|
+
{post.excerpt}
|
|
166
|
+
</p>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Languages */}
|
|
171
|
+
<div className="mb-6">
|
|
172
|
+
<LanguageFlags post={post} />
|
|
173
|
+
</div>
|
|
164
174
|
|
|
165
175
|
{/* Meta Information */}
|
|
166
|
-
<div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700">
|
|
176
|
+
<div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700 mt-auto">
|
|
167
177
|
{/* Author */}
|
|
168
178
|
<div className="flex items-center gap-2">
|
|
169
179
|
{isPostOwner(post) ? (
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import { Search, Filter, Tag } from 'lucide-react';
|
|
9
|
+
import { Search, Filter, Tag, Globe } from 'lucide-react';
|
|
10
10
|
import { PostStatus } from '../../types/post';
|
|
11
11
|
|
|
12
12
|
export interface PostFiltersProps {
|
|
@@ -17,6 +17,9 @@ export interface PostFiltersProps {
|
|
|
17
17
|
categoryFilter: string;
|
|
18
18
|
onCategoryFilterChange: (value: string) => void;
|
|
19
19
|
categories: string[];
|
|
20
|
+
language: string;
|
|
21
|
+
onLanguageChange: (value: string) => void;
|
|
22
|
+
availableLanguages: string[];
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function PostFilters({
|
|
@@ -27,7 +30,20 @@ export function PostFilters({
|
|
|
27
30
|
categoryFilter,
|
|
28
31
|
onCategoryFilterChange,
|
|
29
32
|
categories,
|
|
33
|
+
language,
|
|
34
|
+
onLanguageChange,
|
|
35
|
+
availableLanguages,
|
|
30
36
|
}: PostFiltersProps) {
|
|
37
|
+
const langNames: Record<string, string> = {
|
|
38
|
+
nl: 'Nederlands (NL)',
|
|
39
|
+
en: 'English (EN)',
|
|
40
|
+
sv: 'Svenska (SV)',
|
|
41
|
+
de: 'Deutsch (DE)',
|
|
42
|
+
fr: 'Français (FR)',
|
|
43
|
+
es: 'Español (ES)',
|
|
44
|
+
it: 'Italiano (IT)',
|
|
45
|
+
pt: 'Português (PT)',
|
|
46
|
+
};
|
|
31
47
|
return (
|
|
32
48
|
<div className="flex flex-col sm:flex-row gap-4 mb-6">
|
|
33
49
|
{/* Search Input */}
|
|
@@ -89,6 +105,27 @@ export function PostFilters({
|
|
|
89
105
|
))}
|
|
90
106
|
</select>
|
|
91
107
|
</div>
|
|
108
|
+
|
|
109
|
+
{/* Language Selector */}
|
|
110
|
+
<div className="relative">
|
|
111
|
+
<label htmlFor="blog-post-language-filter" className="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" style={{ clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }}>
|
|
112
|
+
Primary Language
|
|
113
|
+
</label>
|
|
114
|
+
<Globe className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
|
|
115
|
+
<select
|
|
116
|
+
id="blog-post-language-filter"
|
|
117
|
+
name="blog-post-language-filter"
|
|
118
|
+
value={language}
|
|
119
|
+
onChange={(e) => onLanguageChange(e.target.value)}
|
|
120
|
+
className="pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[140px]"
|
|
121
|
+
>
|
|
122
|
+
{availableLanguages.map(lang => (
|
|
123
|
+
<option key={lang} value={lang}>
|
|
124
|
+
{langNames[lang] || lang.toUpperCase()}
|
|
125
|
+
</option>
|
|
126
|
+
))}
|
|
127
|
+
</select>
|
|
128
|
+
</div>
|
|
92
129
|
</div>
|
|
93
130
|
);
|
|
94
131
|
}
|
|
@@ -32,6 +32,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
32
32
|
const [search, setSearch] = useState('');
|
|
33
33
|
const [statusFilter, setStatusFilter] = useState<PostStatus | 'all'>('all');
|
|
34
34
|
const [categoryFilter, setCategoryFilter] = useState<string>('all');
|
|
35
|
+
const [currentLanguage, setCurrentLanguage] = useState<string>(locale || 'nl');
|
|
35
36
|
|
|
36
37
|
// Load view mode preference from localStorage
|
|
37
38
|
const getStoredViewMode = (): ViewMode => {
|
|
@@ -54,7 +55,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
54
55
|
const fetchPosts = async () => {
|
|
55
56
|
try {
|
|
56
57
|
setLoading(true);
|
|
57
|
-
const response = await fetch(
|
|
58
|
+
const response = await fetch(`/api/plugin-blog?admin=true&language=${currentLanguage}`);
|
|
58
59
|
const data = await response.json();
|
|
59
60
|
|
|
60
61
|
if (data.blogs && Array.isArray(data.blogs)) {
|
|
@@ -89,6 +90,9 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
89
90
|
authorId: blogPost.publication.authorId,
|
|
90
91
|
updatedAt: blogPost.updatedAt,
|
|
91
92
|
category: category,
|
|
93
|
+
lang: blogPost.metadata.lang,
|
|
94
|
+
availableLanguages: doc.availableLanguages,
|
|
95
|
+
languages: doc.languages,
|
|
92
96
|
};
|
|
93
97
|
});
|
|
94
98
|
setPosts(postListItems);
|
|
@@ -102,7 +106,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
fetchPosts();
|
|
105
|
-
}, []);
|
|
109
|
+
}, [currentLanguage]);
|
|
106
110
|
|
|
107
111
|
// Extract unique categories from posts
|
|
108
112
|
const categories = React.useMemo(() => {
|
|
@@ -115,6 +119,22 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
115
119
|
return Array.from(categorySet).sort();
|
|
116
120
|
}, [posts]);
|
|
117
121
|
|
|
122
|
+
// Extract unique languages from all posts
|
|
123
|
+
const availableLanguages = React.useMemo(() => {
|
|
124
|
+
const langSet = new Set<string>();
|
|
125
|
+
posts.forEach(post => {
|
|
126
|
+
if (post.availableLanguages) {
|
|
127
|
+
post.availableLanguages.forEach(lang => langSet.add(lang));
|
|
128
|
+
}
|
|
129
|
+
if (post.lang) langSet.add(post.lang);
|
|
130
|
+
});
|
|
131
|
+
// Always include the current locale/language to ensure it's selectable
|
|
132
|
+
if (locale) langSet.add(locale);
|
|
133
|
+
if (currentLanguage) langSet.add(currentLanguage);
|
|
134
|
+
|
|
135
|
+
return Array.from(langSet).sort();
|
|
136
|
+
}, [posts, locale, currentLanguage]);
|
|
137
|
+
|
|
118
138
|
// Filter posts
|
|
119
139
|
const filteredPosts = React.useMemo(() => {
|
|
120
140
|
return posts.filter((post) => {
|
|
@@ -231,6 +251,9 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
|
|
|
231
251
|
categoryFilter={categoryFilter}
|
|
232
252
|
onCategoryFilterChange={setCategoryFilter}
|
|
233
253
|
categories={categories}
|
|
254
|
+
language={currentLanguage}
|
|
255
|
+
onLanguageChange={setCurrentLanguage}
|
|
256
|
+
availableLanguages={availableLanguages}
|
|
234
257
|
/>
|
|
235
258
|
|
|
236
259
|
{/* View Toggle */}
|
|
@@ -10,6 +10,7 @@ import { Calendar, User, UserCheck } from 'lucide-react';
|
|
|
10
10
|
import { Image } from '@jhits/plugin-images';
|
|
11
11
|
import { PostListItem, PostStatus } from '../../types/post';
|
|
12
12
|
import { PostActionsMenu } from './PostActionsMenu';
|
|
13
|
+
import { LanguageFlags } from './LanguageFlags';
|
|
13
14
|
import { useSession } from 'next-auth/react';
|
|
14
15
|
|
|
15
16
|
export interface PostTableProps {
|
|
@@ -21,7 +22,7 @@ export interface PostTableProps {
|
|
|
21
22
|
onDelete: (postId: string) => void;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function getStatusBadgeColor(status: PostStatus) {
|
|
25
|
+
function getStatusBadgeColor(status: PostStatus | 'not-translated') {
|
|
25
26
|
switch (status) {
|
|
26
27
|
case 'published':
|
|
27
28
|
return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
|
|
@@ -31,6 +32,8 @@ function getStatusBadgeColor(status: PostStatus) {
|
|
|
31
32
|
return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
|
|
32
33
|
case 'archived':
|
|
33
34
|
return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
|
|
35
|
+
case 'not-translated':
|
|
36
|
+
return 'bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/20 italic';
|
|
34
37
|
default:
|
|
35
38
|
return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
|
|
36
39
|
}
|
|
@@ -108,6 +111,9 @@ export function PostTable({
|
|
|
108
111
|
<th className="px-6 py-4 text-left text-[10px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400">
|
|
109
112
|
Category
|
|
110
113
|
</th>
|
|
114
|
+
<th className="px-6 py-4 text-left text-[10px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400">
|
|
115
|
+
Languages
|
|
116
|
+
</th>
|
|
111
117
|
<th className="px-6 py-4 text-left text-[10px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400">
|
|
112
118
|
Status
|
|
113
119
|
</th>
|
|
@@ -183,6 +189,11 @@ export function PostTable({
|
|
|
183
189
|
</span>
|
|
184
190
|
</td>
|
|
185
191
|
|
|
192
|
+
{/* Languages */}
|
|
193
|
+
<td className="px-6 py-4">
|
|
194
|
+
<LanguageFlags post={post} />
|
|
195
|
+
</td>
|
|
196
|
+
|
|
186
197
|
{/* Status Badge */}
|
|
187
198
|
<td className="px-6 py-4">
|
|
188
199
|
<span
|