Forum Tenant sistemi hatası düzeltildi.

This commit is contained in:
Sedat ÖZTÜRK 2025-06-24 17:24:08 +03:00
parent 10493bd067
commit 4f2612ab03
11 changed files with 151 additions and 105 deletions

View file

@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.3kkdvt11rc8"
"revision": "0.edm7dfkr7s"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View file

@ -5,7 +5,7 @@ import { useForumData } from './useForumData'
import { ForumView } from './forum/ForumView'
export function Forum() {
const { user, tenantId } = useStoreState((state) => state.auth)
const { user, tenant } = useStoreState((state) => state.auth)
const {
categories,
topics,

View file

@ -5,7 +5,6 @@ import { useForumData } from './useForumData'
import { AdminView } from './admin/AdminView'
export function Management() {
const { user, tenantId } = useStoreState((state) => state.auth)
const {
categories,
topics,
@ -34,12 +33,6 @@ export function Management() {
clearError,
} = useForumData()
const [selectedCategory, setSelectedCategory] = useState<ForumCategory | null>(null)
const [selectedTopic, setSelectedTopic] = useState<ForumTopic | null>(null)
const [forumViewState, setForumViewState] = useState<'categories' | 'topics' | 'posts'>(
'categories',
)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {

View file

@ -1,36 +1,39 @@
import React, { useState } from 'react';
import { Plus, Edit2, Trash2, Lock, Unlock, Eye, EyeOff, Loader2 } from 'lucide-react';
import { ForumCategory } from '@/proxy/forum/forum';
import React, { useState } from 'react'
import { Plus, Edit2, Trash2, Lock, Unlock, Eye, EyeOff, Loader2 } from 'lucide-react'
import { ForumCategory } from '@/proxy/forum/forum'
import { useStoreState } from '@/store/store'
interface CategoryManagementProps {
categories: ForumCategory[];
loading: boolean;
categories: ForumCategory[]
loading: boolean
onCreateCategory: (category: {
name: string;
slug: string;
description: string;
icon: string;
displayOrder: number;
isActive: boolean;
isLocked: boolean;
}) => Promise<void>;
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>;
onDeleteCategory: (id: string) => Promise<void>;
onUpdateCategoryLockState: (id: string) => Promise<void>;
onUpdateCategoryActiveState: (id: string) => Promise<void>;
name: string
slug: string
description: string
icon: string
displayOrder: number
isActive: boolean
isLocked: boolean
tenantId?: string
}) => Promise<void>
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>
onDeleteCategory: (id: string) => Promise<void>
onUpdateCategoryLockState: (id: string) => Promise<void>
onUpdateCategoryActiveState: (id: string) => Promise<void>
}
export function CategoryManagement({
categories,
export function CategoryManagement({
categories,
loading,
onCreateCategory,
onUpdateCategory,
onCreateCategory,
onUpdateCategory,
onDeleteCategory,
onUpdateCategoryLockState,
onUpdateCategoryActiveState
onUpdateCategoryActiveState,
}: CategoryManagementProps) {
const [showCreateForm, setShowCreateForm] = useState(false);
const [editingCategory, setEditingCategory] = useState<ForumCategory | null>(null);
const { tenant } = useStoreState((state) => state.auth)
const [showCreateForm, setShowCreateForm] = useState(false)
const [editingCategory, setEditingCategory] = useState<ForumCategory | null>(null)
const [formData, setFormData] = useState({
name: '',
slug: '',
@ -39,8 +42,9 @@ export function CategoryManagement({
displayOrder: 0,
isActive: true,
isLocked: false,
});
const [submitting, setSubmitting] = useState(false);
tenantId: ''
})
const [submitting, setSubmitting] = useState(false)
const resetForm = () => {
setFormData({
@ -51,32 +55,33 @@ export function CategoryManagement({
displayOrder: 0,
isActive: true,
isLocked: false,
});
setShowCreateForm(false);
setEditingCategory(null);
};
tenantId: ''
})
setShowCreateForm(false)
setEditingCategory(null)
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (submitting) return;
e.preventDefault()
if (submitting) return
try {
setSubmitting(true);
setSubmitting(true)
if (editingCategory) {
await onUpdateCategory(editingCategory.id, formData);
await onUpdateCategory(editingCategory.id, formData)
} else {
await onCreateCategory(formData);
await onCreateCategory(formData)
}
resetForm();
resetForm()
} catch (error) {
console.error('Error submitting form:', error);
console.error('Error submitting form:', error)
} finally {
setSubmitting(false);
setSubmitting(false)
}
};
}
const handleEdit = (category: ForumCategory) => {
setEditingCategory(category);
setEditingCategory(category)
setFormData({
name: category.name,
slug: category.slug,
@ -85,35 +90,40 @@ export function CategoryManagement({
displayOrder: category.displayOrder,
isActive: category.isActive,
isLocked: category.isLocked,
});
setShowCreateForm(true);
};
tenantId: tenant.tenantId ?? ''
})
setShowCreateForm(true)
}
const handleToggleActive = async (category: ForumCategory) => {
try {
await onUpdateCategoryActiveState(category.id);
await onUpdateCategoryActiveState(category.id)
} catch (error) {
console.error('Error toggling category status:', error);
console.error('Error toggling category status:', error)
}
};
}
const handleToggleLocked = async (category: ForumCategory) => {
try {
await onUpdateCategoryLockState(category.id);
await onUpdateCategoryLockState(category.id)
} catch (error) {
console.error('Error toggling category lock:', error);
console.error('Error toggling category lock:', error)
}
};
}
const handleDelete = async (id: string) => {
if (confirm('Are you sure you want to delete this category? This will also delete all topics and posts in this category.')) {
if (
confirm(
'Are you sure you want to delete this category? This will also delete all topics and posts in this category.',
)
) {
try {
await onDeleteCategory(id);
await onDeleteCategory(id)
} catch (error) {
console.error('Error deleting category:', error);
console.error('Error deleting category:', error)
}
}
};
}
return (
<div className="space-y-6">
@ -158,7 +168,7 @@ export function CategoryManagement({
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea
@ -169,7 +179,7 @@ export function CategoryManagement({
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Icon (Emoji)</label>
@ -182,11 +192,15 @@ export function CategoryManagement({
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Display Order</label>
<label className="block text-sm font-medium text-gray-700 mb-1">
Display Order
</label>
<input
type="number"
value={formData.displayOrder}
onChange={(e) => setFormData({ ...formData, displayOrder: parseInt(e.target.value) })}
onChange={(e) =>
setFormData({ ...formData, displayOrder: parseInt(e.target.value) })
}
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
@ -211,7 +225,7 @@ export function CategoryManagement({
</label>
</div>
</div>
<div className="flex justify-end space-x-3">
<button
type="button"
@ -239,7 +253,7 @@ export function CategoryManagement({
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">Categories ({categories.length})</h3>
</div>
{loading ? (
<div className="p-8 text-center">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600" />
@ -258,10 +272,14 @@ export function CategoryManagement({
<div className="flex items-center space-x-2 mb-1">
<h4 className="text-lg font-semibold text-gray-900">{category.name}</h4>
{!category.isActive && (
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded-full">Inactive</span>
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded-full">
Inactive
</span>
)}
{category.isLocked && (
<span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded-full">Locked</span>
<span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded-full">
Locked
</span>
)}
</div>
<p className="text-gray-600 mb-2">{category.description}</p>
@ -272,7 +290,7 @@ export function CategoryManagement({
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => handleToggleActive(category)}
@ -283,9 +301,13 @@ export function CategoryManagement({
}`}
title={category.isActive ? 'Hide Category' : 'Show Category'}
>
{category.isActive ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
{category.isActive ? (
<Eye className="w-4 h-4" />
) : (
<EyeOff className="w-4 h-4" />
)}
</button>
<button
onClick={() => handleToggleLocked(category)}
className={`p-2 rounded-lg transition-colors ${
@ -295,9 +317,13 @@ export function CategoryManagement({
}`}
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
>
{category.isLocked ? <Lock className="w-4 h-4" /> : <Unlock className="w-4 h-4" />}
{category.isLocked ? (
<Lock className="w-4 h-4" />
) : (
<Unlock className="w-4 h-4" />
)}
</button>
<button
onClick={() => handleEdit(category)}
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
@ -305,7 +331,7 @@ export function CategoryManagement({
>
<Edit2 className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(category.id)}
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
@ -321,5 +347,5 @@ export function CategoryManagement({
)}
</div>
</div>
);
}
)
}

View file

@ -3,12 +3,18 @@ import { Plus, Edit2, Trash2, CheckCircle, Circle, Heart, Loader2 } from 'lucide
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'
import { useStoreState } from '@/store/store'
interface PostManagementProps {
posts: ForumPost[]
topics: ForumTopic[]
loading: boolean
onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise<void>
onCreatePost: (post: {
topicId: string
content: string
parentPostId?: string
tenantId?: string
}) => Promise<void>
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>
onDeletePost: (id: string) => Promise<void>
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>
@ -25,11 +31,14 @@ export function PostManagement({
onMarkPostAsAcceptedAnswer,
onUnmarkPostAsAcceptedAnswer,
}: PostManagementProps) {
const { tenant } = useStoreState((state) => state.auth)
const [content, setContent] = useState('')
const [showCreateForm, setShowCreateForm] = useState(false)
const [editingPost, setEditingPost] = useState<ForumPost | null>(null)
const [formData, setFormData] = useState({
topicId: '',
parentPostId: '',
tenantId: '',
isAcceptedAnswer: false,
})
const [submitting, setSubmitting] = useState(false)
@ -37,6 +46,8 @@ export function PostManagement({
const resetForm = () => {
setFormData({
topicId: '',
parentPostId: '',
tenantId: '',
isAcceptedAnswer: false,
})
setShowCreateForm(false)
@ -69,6 +80,8 @@ export function PostManagement({
setEditingPost(post)
setFormData({
topicId: post.topicId,
parentPostId: '',
tenantId: tenant.tenantId ?? '',
isAcceptedAnswer: post.isAcceptedAnswer,
})
setContent(post.content)

View file

@ -13,6 +13,7 @@ import {
Loader2,
} from 'lucide-react'
import { ForumCategory, ForumTopic } from '@/proxy/forum/forum'
import { useStoreState } from '@/store/store'
interface TopicManagementProps {
topics: ForumTopic[]
@ -24,6 +25,7 @@ interface TopicManagementProps {
categoryId: string
isPinned?: boolean
isLocked?: boolean
tenantId?: string
}) => Promise<void>
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>
onDeleteTopic: (id: string) => Promise<void>
@ -49,6 +51,7 @@ export function TopicManagement({
onMarkTopicAsSolved,
onMarkTopicAsUnsolved,
}: TopicManagementProps) {
const { tenant } = useStoreState((state) => state.auth)
const [showCreateForm, setShowCreateForm] = useState(false)
const [editingTopic, setEditingTopic] = useState<ForumTopic | null>(null)
const [formData, setFormData] = useState({
@ -58,6 +61,7 @@ export function TopicManagement({
isPinned: false,
isLocked: false,
isSolved: false,
tenantId: '',
})
const [submitting, setSubmitting] = useState(false)
@ -69,6 +73,7 @@ export function TopicManagement({
isPinned: false,
isLocked: false,
isSolved: false,
tenantId: '',
})
setShowCreateForm(false)
setEditingTopic(null)
@ -102,6 +107,7 @@ export function TopicManagement({
isPinned: topic.isPinned,
isLocked: topic.isLocked,
isSolved: topic.isSolved,
tenantId: tenant.tenantId ?? ''
})
setShowCreateForm(true)
}

View file

@ -2,21 +2,23 @@ import React, { useState } from 'react';
import { X } from 'lucide-react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import { useStoreState } from '@/store/store';
interface CreatePostModalProps {
onClose: () => void;
onSubmit: (data: { content: string }) => void;
parentPostId?: string;
onSubmit: (data: { content: string, parentPostId?: string, tenantId?: string }) => void;
parentPostId: string
}
export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostModalProps) {
const [content, setContent] = useState('');
const { tenant } = useStoreState((state) => state.auth)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const plainText = content.replace(/<[^>]+>/g, '').trim(); // HTML etiketlerini temizle
if (plainText) {
onSubmit({ content });
onSubmit({ content, parentPostId, tenantId: tenant.tenantId });
}
};

View file

@ -1,19 +1,21 @@
import React, { useState } from 'react';
import { X } from 'lucide-react';
import { useStoreState } from '@/store/store';
interface CreateTopicModalProps {
onClose: () => void;
onSubmit: (data: { title: string; content: string }) => void;
onSubmit: (data: { title: string; content: string, tenantId?: string }) => void;
}
export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const { tenant } = useStoreState((state) => state.auth)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (title.trim() && content.trim()) {
onSubmit({ title: title.trim(), content: content.trim() });
onSubmit({ title: title.trim(), content: content.trim(), tenantId: tenant.tenantId });
}
};

View file

@ -18,10 +18,6 @@ export function ForumPostCard({
isFirst = false,
isLiked = false,
}: PostCardProps) {
const handleLike = () => {
onLike(post.id, isFirst)
}
const formatDate = (value: string | Date) => {
const date = value instanceof Date ? value : new Date(value)
if (isNaN(date.getTime())) return 'Invalid Date'
@ -67,7 +63,10 @@ export function ForumPostCard({
</div>
<div className="prose prose-sm max-w-none mb-4">
<p className="text-gray-700 whitespace-pre-wrap" dangerouslySetInnerHTML={{ __html: post.content }} />
<p
className="text-gray-700 whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</div>
<div className="flex items-center space-x-4">
@ -83,13 +82,13 @@ export function ForumPostCard({
<span>{post.likeCount}</span>
</button>
<button
{!isFirst && (<button
onClick={() => onReply(post.id)}
className="flex items-center space-x-1 px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors"
>
<Reply className="w-4 h-4" />
<span>Reply</span>
</button>
</button>)}
</div>
</div>
</div>

View file

@ -9,6 +9,7 @@ import { buildPostTree } from './utils'
import { ForumPostCard } from './ForumPostCard'
import { ForumCategoryCard } from './ForumCategoryCard'
import { ForumTopicCard } from './ForumTopicCard'
import { useStoreState } from '@/store/store'
interface ForumViewProps {
categories: ForumCategory[]
@ -21,8 +22,14 @@ interface ForumViewProps {
categoryId: string
isPinned?: boolean
isLocked?: boolean
tenantId?: string
}) => Promise<void>
onCreatePost: (post: {
topicId: string
content: string
parentPostId?: string
tenantId?: string
}) => Promise<void>
onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise<void>
onLikePost: (id: string) => Promise<void>
onUnlikePost: (id: string) => Promise<void>
currentUserId: string
@ -68,6 +75,7 @@ export function ForumView({
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false)
const [postLikeCounts, setPostLikeCounts] = useState<Record<string, number>>({})
const { tenant } = useStoreState((state) => state.auth)
const handleSearchCategorySelect = (category: ForumCategory) => {
if (onCategorySelect) onCategorySelect(category)
@ -201,6 +209,7 @@ export function ForumView({
categoryId: selectedCategory.id,
isPinned: false,
isLocked: false,
tenantId: tenant.tenantId,
})
setShowCreateTopic(false)
} catch (error) {
@ -215,10 +224,11 @@ export function ForumView({
await onCreatePost({
topicId: selectedTopic.id,
content: postData.content,
parentPostId: replyToPostId, // buraya dikkat
parentPostId: replyToPostId,
tenantId: tenant.tenantId,
})
setShowCreatePost(false)
setReplyToPostId(undefined) // temizle
setReplyToPostId(undefined)
} catch (error) {
console.error('Error creating post:', error)
}
@ -271,13 +281,6 @@ export function ForumView({
}
}
// 🧠 Helper function to read initial like count
const getInitialLikeCount = (postId: string) => {
if (selectedTopic?.id === postId) return selectedTopic.likeCount
const post = posts.find((p) => p.id === postId)
return post?.likeCount ?? 0
}
const handleReply = (postId: string) => {
setReplyToPostId(postId)
setShowCreatePost(true)
@ -446,9 +449,10 @@ export function ForumView({
authorId: selectedTopic.authorId,
authorName: selectedTopic.authorName,
likeCount: postLikeCounts[selectedTopic.id] ?? selectedTopic.likeCount,
isAcceptedAnswer: false,
isAcceptedAnswer: false,
parentPostId: undefined,
creationTime: selectedTopic.creationTime,
tenantId: selectedTopic.tenantId
}}
onLike={handleLike}
onReply={handleReply}

View file

@ -248,9 +248,10 @@ export function SearchModal({
<div className="font-medium text-gray-900 text-sm line-clamp-1">
{getTopicTitle(post.topicId)}
</div>
<div className="text-sm text-gray-600 line-clamp-2 mt-1">
{post.content}
</div>
<div
className="text-sm text-gray-600 line-clamp-2 mt-1"
dangerouslySetInnerHTML={{ __html: post.content }}
></div>
<div className="text-xs text-gray-500 mt-1">
by {post.authorName} {formatDate(post.creationTime)}
</div>