Blog sistemindeki güncellemeler4

This commit is contained in:
Sedat ÖZTÜRK 2025-06-20 17:55:55 +03:00
parent d59d1009e7
commit 3086927146
13 changed files with 396 additions and 135 deletions

File diff suppressed because one or more lines are too long

View file

@ -38,7 +38,7 @@ const Header: React.FC = () => {
{ name: t("nav.products"), path: "/products", icon: Package },
{ name: t("nav.services"), path: "/services", icon: Briefcase },
{ name: t("nav.blog"), path: "/blog", icon: BookOpen },
{ name: t("nav.forum") || "Forum", path: "/forum", icon: MessageSquare, protected: true },
{ name: t("nav.forum"), path: "/forum", icon: MessageSquare, protected: true },
{ name: t("nav.contact"), path: "/contact", icon: Phone },
];

File diff suppressed because one or more lines are too long

View file

@ -11,7 +11,7 @@ const About: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div className="absolute inset-0 opacity-20" style={{
backgroundImage: 'url("https://images.pexels.com/photos/3183183/pexels-photo-3183183.jpeg?auto=compress&cs=tinysrgb&w=1920")',
backgroundSize: 'cover',

View file

@ -73,12 +73,12 @@ const Blog = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
'url("https://images.pexels.com/photos/3183160/pexels-photo-3183160.jpeg?auto=compress&cs=tinysrgb&w=1920")',
'url("https://images.pexels.com/photos/3183164/pexels-photo-3183164.jpeg?auto=compress&cs=tinysrgb&w=1920")',
backgroundSize: "cover",
backgroundPosition: "center",
}}

View file

@ -103,7 +103,7 @@ const BlogDetail: React.FC = () => {
})}
</div>
</div>
<div className="prose max-w-none text-gray-800" dangerouslySetInnerHTML={{ __html: blogPost.content || "" }} />
<div className="prose max-w-none text-gray-800" dangerouslySetInnerHTML={{ __html: t(blogPost.content!!) || "" }} />
</div>
</div>
);

View file

@ -73,7 +73,7 @@ const Contact: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{

View file

@ -48,7 +48,7 @@ const Forum: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{

View file

@ -127,7 +127,7 @@ const Products: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{

View file

@ -133,7 +133,7 @@ const Services: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-24">
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{

View file

@ -73,7 +73,6 @@ class AuthService {
formData.append('grant_type', data.grant_type || 'password');
formData.append('scope', data.scope || 'offline_access Platform');
formData.append('client_id', data.client_id || 'Platform_App');
// Client secret kaldırıldı - public client olarak çalışıyor
const response = await apiClient.post<LoginResponse>('/connect/token', formData, {
headers: {

View file

@ -1,7 +1,7 @@
import axios from 'axios';
// API Base URL
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:44328';
export const API_BASE_URL = import.meta.env.VITE_API_URL;
// Axios instance
export const apiClient = axios.create({

View file

@ -35,6 +35,7 @@ import { CheckBox } from 'devextreme-react'
import { Checkbox } from '@/components/ui'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ConfirmDialog } from '@/components/shared'
const validationSchema = Yup.object().shape({
title: Yup.string().required(),
@ -87,7 +88,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
Veriler yüklenirken hata oluştu
{translate('::Error:Loading')}
</Notification>,
{
placement: 'top-center',
@ -113,7 +114,7 @@ const BlogManagement = () => {
await blogService.deletePost(id)
toast.push(
<Notification title="Başarılı" type="success">
Blog yazısı silindi
{translate('::KayitSilindi')}
</Notification>,
{
placement: 'top-center',
@ -123,7 +124,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
Silme işlemi başarısız
{translate('::Error:Deleting')}
</Notification>,
{
placement: 'top-center',
@ -149,7 +150,7 @@ const BlogManagement = () => {
await blogService.updatePost(editingPost.id, data)
toast.push(
<Notification title="Başarılı" type="success">
Blog yazısı güncellendi
{translate('::KayitGuncellendi')}
</Notification>,
{
placement: 'top-center',
@ -159,7 +160,7 @@ const BlogManagement = () => {
await blogService.createPost(data)
toast.push(
<Notification title="Başarılı" type="success">
Blog yazısı oluşturuldu
{translate('::KayitEklendi')}
</Notification>,
{
placement: 'top-center',
@ -172,7 +173,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
İşlem başarısız
{translate('::IslemBasarisiz')}
</Notification>,
{
placement: 'top-center',
@ -189,7 +190,7 @@ const BlogManagement = () => {
await blogService.unpublishPost(post.id)
toast.push(
<Notification title="Başarılı" type="success">
Yayından kaldırıldı
{translate('::YayinKaldirildi')}
</Notification>,
{
placement: 'top-center',
@ -199,7 +200,7 @@ const BlogManagement = () => {
await blogService.publishPost(post.id)
toast.push(
<Notification title="Başarılı" type="success">
Yayınlandı
{translate('::Yayinlandi')}
</Notification>,
{
placement: 'top-center',
@ -210,7 +211,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
İşlem başarısız
{translate('::IslemBasarisiz')}
</Notification>,
{
placement: 'top-center',
@ -236,7 +237,7 @@ const BlogManagement = () => {
await blogService.deleteCategory(id)
toast.push(
<Notification title="Başarılı" type="success">
Kategori silindi
{translate('::KayitSilindi')}
</Notification>,
{
placement: 'top-center',
@ -246,7 +247,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
Silme işlemi başarısız
{translate('::IslemBasarisiz')}
</Notification>,
{
placement: 'top-center',
@ -270,7 +271,7 @@ const BlogManagement = () => {
await blogService.updateCategory(editingCategory.id, data)
toast.push(
<Notification title="Başarılı" type="success">
Kategori güncellendi
{translate('::KayitGuncellendi')}
</Notification>,
{
placement: 'top-center',
@ -280,7 +281,7 @@ const BlogManagement = () => {
await blogService.createCategory(data)
toast.push(
<Notification title="Başarılı" type="success">
Kategori oluşturuldu
{translate('::KayitEklendi')}
</Notification>,
{
placement: 'top-center',
@ -293,7 +294,7 @@ const BlogManagement = () => {
} catch (error) {
toast.push(
<Notification title="Hata" type="danger">
İşlem başarısız
{translate('::IslemBasarisiz')}
</Notification>,
{
placement: 'top-center',
@ -344,6 +345,17 @@ const BlogManagement = () => {
isActive: true,
}
const [confirmDeletePost, setConfirmDeletePost] = useState<BlogPost | null>(null)
const [confirmDeleteCategory, setConfirmDeleteCategory] = useState<BlogCategory | null>(null)
const askDeletePost = (post: BlogPost) => {
setConfirmDeletePost(post)
}
const askDeleteCategory = (category: BlogCategory) => {
setConfirmDeleteCategory(category)
}
return (
<>
<Helmet
@ -352,37 +364,43 @@ const BlogManagement = () => {
defaultTitle="Kurs Platform"
></Helmet>
<Card>
<div className="mb-4">
<div className="flex gap-4 border-b">
<button
className={`pb-2 px-1 ${activeTab === 'posts' ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-600'}`}
onClick={() => setActiveTab('posts')}
>
<b>Blog Yazıları</b>
</button>
<button
className={`pb-2 px-1 ${activeTab === 'categories' ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-600'}`}
onClick={() => setActiveTab('categories')}
>
<b>Kategoriler</b>
</button>
</div>
<div className="flex gap-2 border-b">
<button
className={`p-2 rounded-t-md transition ${
activeTab === 'posts'
? 'bg-blue-100 text-blue-600 font-bold'
: 'text-gray-600 hover:bg-gray-100 font-semibold'
}`}
onClick={() => setActiveTab('posts')}
>
{translate('::blog.posts.title')}
</button>
<button
className={`p-2 rounded-t-md transition ${
activeTab === 'categories'
? 'bg-blue-100 text-blue-600 font-bold'
: 'text-gray-600 hover:bg-gray-100 font-semibold'
}`}
onClick={() => setActiveTab('categories')}
>
{translate('::blog.posts.categories')}
</button>
</div>
{activeTab === 'posts' ? (
<Table compact>
<THead>
<Tr>
<Th>Başlık</Th>
<Th>Slug</Th>
<Th>Kategori</Th>
<Th>Yazar</Th>
<Th>Yayın Tarihi</Th>
<Th>Durum</Th>
<Th>{translate('::blog.posts.post.title')}</Th>
<Th>{translate('::blog.posts.post.slug')}</Th>
<Th>{translate('::blog.posts.post.category')}</Th>
<Th>{translate('::blog.posts.post.author')}</Th>
<Th>{translate('::blog.posts.post.publishDate')}</Th>
<Th>{translate('::blog.posts.post.status')}</Th>
<Th>
{' '}
<Button variant="solid" size="xs" icon={<HiPlus />} onClick={handleCreate}>
Yeni
{translate('::New')}
</Button>
</Th>
</Tr>
@ -391,7 +409,7 @@ const BlogManagement = () => {
{loading ? (
<Tr>
<Td colSpan={7} className="text-center">
Yükleniyor...
{translate('::Loading')}
</Td>
</Tr>
) : (
@ -407,17 +425,17 @@ const BlogManagement = () => {
: '-'}
</Td>
<Td>
<Switcher checked={post.isPublished} onChange={() => handlePublish(post)} />
<Switcher className="switcher-sm" checked={post.isPublished} onChange={() => handlePublish(post)} />
</Td>
<Td>
<div className="flex gap-2">
<Button size="sm" icon={<HiPencil />} onClick={() => handleEdit(post)} />
<Button size="xs" icon={<HiPencil />} onClick={() => handleEdit(post)} />
<Button
size="sm"
size="xs"
variant="solid"
color="red-600"
icon={<HiTrash />}
onClick={() => handleDelete(post.id)}
onClick={() => askDeletePost(post)}
/>
</div>
</Td>
@ -430,11 +448,11 @@ const BlogManagement = () => {
<Table compact>
<THead>
<Tr>
<Th>İsim</Th>
<Th>Slug</Th>
<Th>ıklama</Th>
<Th>Yazı Sayısı</Th>
<Th>Durum</Th>
<Th>{translate('::blog.posts.categories.name')}</Th>
<Th>{translate('::blog.posts.categories.slug')}</Th>
<Th>{translate('::blog.posts.categories.description')}</Th>
<Th>{translate('::blog.posts.categories.count')}</Th>
<Th>{translate('::blog.posts.categories.status')}</Th>
<Th>
<Button
variant="solid"
@ -442,7 +460,7 @@ const BlogManagement = () => {
icon={<HiPlus />}
onClick={handleCreateCategory}
>
Yeni
{translate('::New')}
</Button>
</Th>
</Tr>
@ -451,7 +469,7 @@ const BlogManagement = () => {
{loading ? (
<Tr>
<Td colSpan={5} className="text-center">
Yükleniyor...
{translate('::Loading')}
</Td>
</Tr>
) : (
@ -475,16 +493,16 @@ const BlogManagement = () => {
<Td>
<div className="flex gap-2">
<Button
size="sm"
size="xs"
icon={<HiPencil />}
onClick={() => handleEditCategory(category)}
/>
<Button
size="sm"
size="xs"
variant="solid"
color="red-600"
icon={<HiTrash />}
onClick={() => handleDeleteCategory(category.id)}
onClick={() => askDeleteCategory(category)}
/>
</div>
</Td>
@ -503,7 +521,9 @@ const BlogManagement = () => {
onRequestClose={() => setModalVisible(false)}
width={1000}
>
<h5 className="mb-4">{editingPost ? 'Blog Yazısını Düzenle' : 'Yeni Blog Yazısı'}</h5>
<h5 className="mb-4">
{editingPost ? translate('::blog.posts.edittitle') : translate('::blog.posts.newtitle')}
</h5>
<Formik
initialValues={initialValues}
@ -516,22 +536,16 @@ const BlogManagement = () => {
<FormContainer>
<FormItem
asterisk
label="Başlık"
label={translate('::blog.posts.post.title')}
invalid={errors.title && touched.title}
errorMessage={errors.title}
>
<Field
type="text"
name="title"
placeholder="Blog başlığı"
component={Input}
autoFocus={true}
/>
<Field type="text" name="title" component={Input} autoFocus={true} />
</FormItem>
<FormItem
asterisk
label="Slug"
label={translate('::blog.posts.post.slug')}
invalid={errors.title && touched.title}
errorMessage={errors.title}
>
@ -540,22 +554,16 @@ const BlogManagement = () => {
<FormItem
asterisk
label="Özet"
label={translate('::blog.posts.post.summary')}
invalid={errors.summary && touched.summary}
errorMessage={errors.summary}
>
<Field
name="summary"
placeholder="Kısa açıklama"
component={Input}
textArea={true}
rows={3}
/>
<Field name="summary" component={Input} />
</FormItem>
<FormItem
asterisk
label="Kategori"
label={translate('::blog.posts.post.category')}
invalid={errors.categoryId && touched.categoryId}
errorMessage={errors.categoryId}
>
@ -573,26 +581,21 @@ const BlogManagement = () => {
</Field>
</FormItem>
<FormItem label="Etiketler - Virgül ile ayırarak yazınız">
<FormItem label={translate('::blog.posts.post.tags')}>
<Field
type="text"
name="tags"
placeholder="örn: react, javascript, web"
placeholder="react, javascript, web"
component={Input}
/>
</FormItem>
<FormItem label="Kapak Görseli">
<Field
type="text"
name="coverImage"
placeholder="Görsel URL'si"
component={Input}
/>
<FormItem label={translate('::blog.posts.post.image')}>
<Field type="text" name="coverImage" component={Input} />
</FormItem>
<FormItem
label="İçerik"
label={translate('::blog.posts.post.content')}
asterisk
invalid={!!errors.content}
errorMessage={errors.content}
@ -606,7 +609,7 @@ const BlogManagement = () => {
</FormItem>
<FormItem
label="Durum"
label={translate('::blog.posts.post.status')}
invalid={errors.isPublished && touched.isPublished}
errorMessage={errors.isPublished}
>
@ -616,10 +619,12 @@ const BlogManagement = () => {
<FormItem>
<div className="flex gap-2">
<Button variant="solid" type="submit" loading={isSubmitting}>
{editingPost ? 'Güncelle' : 'Oluştur'}
{editingPost
? translate('::blog.posts.post.update')
: translate('::blog.posts.post.create')}
</Button>
<Button variant="plain" onClick={() => setModalVisible(false)}>
İptal
{translate('::Cancel')}
</Button>
</div>
</FormItem>
@ -636,7 +641,11 @@ const BlogManagement = () => {
onRequestClose={() => setCategoryModalVisible(false)}
width={600}
>
<h5 className="mb-4">{editingCategory ? 'Kategoriyi Düzenle' : 'Yeni Kategori'}</h5>
<h5 className="mb-4">
{editingCategory
? translate('::blog.posts.categories.edittitle')
: translate('::blog.posts.categories.newtitle')}
</h5>
<Formik
initialValues={initialCategoryValues}
@ -649,26 +658,20 @@ const BlogManagement = () => {
<FormContainer>
<FormItem
asterisk
label="İsim"
label={translate('::blog.posts.categories.name')}
invalid={errors.name && touched.name}
errorMessage={errors.name}
>
<Field
autoFocus={true}
type="text"
name="name"
placeholder="Kategori ismi"
component={Input}
/>
<Field autoFocus={true} type="text" name="name" component={Input} />
</FormItem>
<FormItem
asterisk
label="Slug"
label={translate('::blog.posts.categories.slug')}
invalid={errors.slug && touched.slug}
errorMessage={errors.slug}
>
<Field type="text" name="slug" placeholder="kategori-slug" component={Input} />
<Field type="text" name="slug" component={Input} />
</FormItem>
<FormItem
@ -677,24 +680,21 @@ const BlogManagement = () => {
errorMessage={errors.description}
>
<Field
name="description"
placeholder="Kategori açıklaması"
name={translate('::blog.posts.categories.description')}
component={Input}
textArea={true}
rows={3}
/>
</FormItem>
<FormItem label="İkon (Emoji)">
<Field type="text" name="icon" placeholder="📚" component={Input} />
<FormItem label={translate('::blog.posts.categories.icon')}>
<Field type="text" name="icon" component={Input} />
</FormItem>
<FormItem label="Sıralama">
<FormItem label={translate('::blog.posts.categories.order')}>
<Field type="number" name="displayOrder" placeholder="0" component={Input} />
</FormItem>
<FormItem
label="Durum"
label={translate('::blog.posts.categories.status')}
invalid={errors.isActive && touched.isActive}
errorMessage={errors.isActive}
>
@ -704,10 +704,12 @@ const BlogManagement = () => {
<FormItem>
<div className="flex gap-2">
<Button variant="solid" type="submit" loading={isSubmitting}>
{editingCategory ? 'Güncelle' : 'Oluştur'}
{editingCategory
? translate('::blog.posts.categories.update')
: translate('::blog.posts.categories.create')}
</Button>
<Button variant="plain" onClick={() => setCategoryModalVisible(false)}>
İptal
{translate('::Cancel')}
</Button>
</div>
</FormItem>
@ -716,6 +718,50 @@ const BlogManagement = () => {
)}
</Formik>
</Dialog>
{/* Post Silme Onayı */}
<ConfirmDialog
isOpen={!!confirmDeletePost}
type="danger"
title={translate('::DeleteConfirmation')}
confirmText={translate('::Delete')}
cancelText={translate('::Cancel')}
confirmButtonColor="red-600"
onCancel={() => setConfirmDeletePost(null)}
onConfirm={async () => {
if (confirmDeletePost) {
await handleDelete(confirmDeletePost.id)
setConfirmDeletePost(null)
}
}}
>
<p>
<span className="font-semibold">{confirmDeletePost?.title}</span>{' '}
{translate('::DeleteConfirmation')}
</p>
</ConfirmDialog>
{/* Kategori Silme Onayı */}
<ConfirmDialog
isOpen={!!confirmDeleteCategory}
type="danger"
title={translate('::DeleteConfirmation')}
confirmText={translate('::Delete')}
cancelText={translate('::Cancel')}
confirmButtonColor="red-600"
onCancel={() => setConfirmDeleteCategory(null)}
onConfirm={async () => {
if (confirmDeleteCategory) {
await handleDeleteCategory(confirmDeleteCategory.id)
setConfirmDeleteCategory(null)
}
}}
>
<p>
<span className="font-semibold">{confirmDeleteCategory?.name}</span>{' '}
{translate('::DeleteConfirmation')}
</p>
</ConfirmDialog>
</>
)
}