Forum Tenant sistemi hatası düzeltildi.
This commit is contained in:
parent
10493bd067
commit
4f2612ab03
11 changed files with 151 additions and 105 deletions
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.3kkdvt11rc8"
|
"revision": "0.edm7dfkr7s"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useForumData } from './useForumData'
|
||||||
import { ForumView } from './forum/ForumView'
|
import { ForumView } from './forum/ForumView'
|
||||||
|
|
||||||
export function Forum() {
|
export function Forum() {
|
||||||
const { user, tenantId } = useStoreState((state) => state.auth)
|
const { user, tenant } = useStoreState((state) => state.auth)
|
||||||
const {
|
const {
|
||||||
categories,
|
categories,
|
||||||
topics,
|
topics,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { useForumData } from './useForumData'
|
||||||
import { AdminView } from './admin/AdminView'
|
import { AdminView } from './admin/AdminView'
|
||||||
|
|
||||||
export function Management() {
|
export function Management() {
|
||||||
const { user, tenantId } = useStoreState((state) => state.auth)
|
|
||||||
const {
|
const {
|
||||||
categories,
|
categories,
|
||||||
topics,
|
topics,
|
||||||
|
|
@ -34,12 +33,6 @@ export function Management() {
|
||||||
clearError,
|
clearError,
|
||||||
} = useForumData()
|
} = useForumData()
|
||||||
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<ForumCategory | null>(null)
|
|
||||||
const [selectedTopic, setSelectedTopic] = useState<ForumTopic | null>(null)
|
|
||||||
const [forumViewState, setForumViewState] = useState<'categories' | 'topics' | 'posts'>(
|
|
||||||
'categories',
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,39 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react'
|
||||||
import { Plus, Edit2, Trash2, Lock, Unlock, Eye, EyeOff, Loader2 } from 'lucide-react';
|
import { Plus, Edit2, Trash2, Lock, Unlock, Eye, EyeOff, Loader2 } from 'lucide-react'
|
||||||
import { ForumCategory } from '@/proxy/forum/forum';
|
import { ForumCategory } from '@/proxy/forum/forum'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
||||||
interface CategoryManagementProps {
|
interface CategoryManagementProps {
|
||||||
categories: ForumCategory[];
|
categories: ForumCategory[]
|
||||||
loading: boolean;
|
loading: boolean
|
||||||
onCreateCategory: (category: {
|
onCreateCategory: (category: {
|
||||||
name: string;
|
name: string
|
||||||
slug: string;
|
slug: string
|
||||||
description: string;
|
description: string
|
||||||
icon: string;
|
icon: string
|
||||||
displayOrder: number;
|
displayOrder: number
|
||||||
isActive: boolean;
|
isActive: boolean
|
||||||
isLocked: boolean;
|
isLocked: boolean
|
||||||
}) => Promise<void>;
|
tenantId?: string
|
||||||
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>;
|
}) => Promise<void>
|
||||||
onDeleteCategory: (id: string) => Promise<void>;
|
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>
|
||||||
onUpdateCategoryLockState: (id: string) => Promise<void>;
|
onDeleteCategory: (id: string) => Promise<void>
|
||||||
onUpdateCategoryActiveState: (id: string) => Promise<void>;
|
onUpdateCategoryLockState: (id: string) => Promise<void>
|
||||||
|
onUpdateCategoryActiveState: (id: string) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CategoryManagement({
|
export function CategoryManagement({
|
||||||
categories,
|
categories,
|
||||||
loading,
|
loading,
|
||||||
onCreateCategory,
|
onCreateCategory,
|
||||||
onUpdateCategory,
|
onUpdateCategory,
|
||||||
onDeleteCategory,
|
onDeleteCategory,
|
||||||
onUpdateCategoryLockState,
|
onUpdateCategoryLockState,
|
||||||
onUpdateCategoryActiveState
|
onUpdateCategoryActiveState,
|
||||||
}: CategoryManagementProps) {
|
}: CategoryManagementProps) {
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
const [editingCategory, setEditingCategory] = useState<ForumCategory | null>(null);
|
const [showCreateForm, setShowCreateForm] = useState(false)
|
||||||
|
const [editingCategory, setEditingCategory] = useState<ForumCategory | null>(null)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
slug: '',
|
slug: '',
|
||||||
|
|
@ -39,8 +42,9 @@ export function CategoryManagement({
|
||||||
displayOrder: 0,
|
displayOrder: 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
});
|
tenantId: ''
|
||||||
const [submitting, setSubmitting] = useState(false);
|
})
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -51,32 +55,33 @@ export function CategoryManagement({
|
||||||
displayOrder: 0,
|
displayOrder: 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
});
|
tenantId: ''
|
||||||
setShowCreateForm(false);
|
})
|
||||||
setEditingCategory(null);
|
setShowCreateForm(false)
|
||||||
};
|
setEditingCategory(null)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
if (submitting) return;
|
if (submitting) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSubmitting(true);
|
setSubmitting(true)
|
||||||
if (editingCategory) {
|
if (editingCategory) {
|
||||||
await onUpdateCategory(editingCategory.id, formData);
|
await onUpdateCategory(editingCategory.id, formData)
|
||||||
} else {
|
} else {
|
||||||
await onCreateCategory(formData);
|
await onCreateCategory(formData)
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error submitting form:', error);
|
console.error('Error submitting form:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (category: ForumCategory) => {
|
const handleEdit = (category: ForumCategory) => {
|
||||||
setEditingCategory(category);
|
setEditingCategory(category)
|
||||||
setFormData({
|
setFormData({
|
||||||
name: category.name,
|
name: category.name,
|
||||||
slug: category.slug,
|
slug: category.slug,
|
||||||
|
|
@ -85,35 +90,40 @@ export function CategoryManagement({
|
||||||
displayOrder: category.displayOrder,
|
displayOrder: category.displayOrder,
|
||||||
isActive: category.isActive,
|
isActive: category.isActive,
|
||||||
isLocked: category.isLocked,
|
isLocked: category.isLocked,
|
||||||
});
|
tenantId: tenant.tenantId ?? ''
|
||||||
setShowCreateForm(true);
|
})
|
||||||
};
|
setShowCreateForm(true)
|
||||||
|
}
|
||||||
|
|
||||||
const handleToggleActive = async (category: ForumCategory) => {
|
const handleToggleActive = async (category: ForumCategory) => {
|
||||||
try {
|
try {
|
||||||
await onUpdateCategoryActiveState(category.id);
|
await onUpdateCategoryActiveState(category.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error toggling category status:', error);
|
console.error('Error toggling category status:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleToggleLocked = async (category: ForumCategory) => {
|
const handleToggleLocked = async (category: ForumCategory) => {
|
||||||
try {
|
try {
|
||||||
await onUpdateCategoryLockState(category.id);
|
await onUpdateCategoryLockState(category.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error toggling category lock:', error);
|
console.error('Error toggling category lock:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
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 {
|
try {
|
||||||
await onDeleteCategory(id);
|
await onDeleteCategory(id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting category:', error);
|
console.error('Error deleting category:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -158,7 +168,7 @@ export function CategoryManagement({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -169,7 +179,7 @@ export function CategoryManagement({
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Icon (Emoji)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Icon (Emoji)</label>
|
||||||
|
|
@ -182,11 +192,15 @@ export function CategoryManagement({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.displayOrder}
|
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"
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -211,7 +225,7 @@ export function CategoryManagement({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3">
|
<div className="flex justify-end space-x-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -239,7 +253,7 @@ export function CategoryManagement({
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">Categories ({categories.length})</h3>
|
<h3 className="text-lg font-semibold text-gray-900">Categories ({categories.length})</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-600" />
|
<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">
|
<div className="flex items-center space-x-2 mb-1">
|
||||||
<h4 className="text-lg font-semibold text-gray-900">{category.name}</h4>
|
<h4 className="text-lg font-semibold text-gray-900">{category.name}</h4>
|
||||||
{!category.isActive && (
|
{!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 && (
|
{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>
|
</div>
|
||||||
<p className="text-gray-600 mb-2">{category.description}</p>
|
<p className="text-gray-600 mb-2">{category.description}</p>
|
||||||
|
|
@ -272,7 +290,7 @@ export function CategoryManagement({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleActive(category)}
|
onClick={() => handleToggleActive(category)}
|
||||||
|
|
@ -283,9 +301,13 @@ export function CategoryManagement({
|
||||||
}`}
|
}`}
|
||||||
title={category.isActive ? 'Hide Category' : 'Show Category'}
|
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>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleLocked(category)}
|
onClick={() => handleToggleLocked(category)}
|
||||||
className={`p-2 rounded-lg transition-colors ${
|
className={`p-2 rounded-lg transition-colors ${
|
||||||
|
|
@ -295,9 +317,13 @@ export function CategoryManagement({
|
||||||
}`}
|
}`}
|
||||||
title={category.isLocked ? 'Unlock Category' : 'Lock Category'}
|
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>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(category)}
|
onClick={() => handleEdit(category)}
|
||||||
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
|
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" />
|
<Edit2 className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(category.id)}
|
onClick={() => handleDelete(category.id)}
|
||||||
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
|
||||||
|
|
@ -321,5 +347,5 @@ export function CategoryManagement({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,18 @@ import { Plus, Edit2, Trash2, CheckCircle, Circle, Heart, Loader2 } from 'lucide
|
||||||
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
||||||
import ReactQuill from 'react-quill'
|
import ReactQuill from 'react-quill'
|
||||||
import 'react-quill/dist/quill.snow.css'
|
import 'react-quill/dist/quill.snow.css'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
||||||
interface PostManagementProps {
|
interface PostManagementProps {
|
||||||
posts: ForumPost[]
|
posts: ForumPost[]
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
loading: boolean
|
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>
|
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>
|
||||||
onDeletePost: (id: string) => Promise<void>
|
onDeletePost: (id: string) => Promise<void>
|
||||||
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>
|
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>
|
||||||
|
|
@ -25,11 +31,14 @@ export function PostManagement({
|
||||||
onMarkPostAsAcceptedAnswer,
|
onMarkPostAsAcceptedAnswer,
|
||||||
onUnmarkPostAsAcceptedAnswer,
|
onUnmarkPostAsAcceptedAnswer,
|
||||||
}: PostManagementProps) {
|
}: PostManagementProps) {
|
||||||
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
const [content, setContent] = useState('')
|
const [content, setContent] = useState('')
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false)
|
const [showCreateForm, setShowCreateForm] = useState(false)
|
||||||
const [editingPost, setEditingPost] = useState<ForumPost | null>(null)
|
const [editingPost, setEditingPost] = useState<ForumPost | null>(null)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
topicId: '',
|
topicId: '',
|
||||||
|
parentPostId: '',
|
||||||
|
tenantId: '',
|
||||||
isAcceptedAnswer: false,
|
isAcceptedAnswer: false,
|
||||||
})
|
})
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
@ -37,6 +46,8 @@ export function PostManagement({
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
topicId: '',
|
topicId: '',
|
||||||
|
parentPostId: '',
|
||||||
|
tenantId: '',
|
||||||
isAcceptedAnswer: false,
|
isAcceptedAnswer: false,
|
||||||
})
|
})
|
||||||
setShowCreateForm(false)
|
setShowCreateForm(false)
|
||||||
|
|
@ -69,6 +80,8 @@ export function PostManagement({
|
||||||
setEditingPost(post)
|
setEditingPost(post)
|
||||||
setFormData({
|
setFormData({
|
||||||
topicId: post.topicId,
|
topicId: post.topicId,
|
||||||
|
parentPostId: '',
|
||||||
|
tenantId: tenant.tenantId ?? '',
|
||||||
isAcceptedAnswer: post.isAcceptedAnswer,
|
isAcceptedAnswer: post.isAcceptedAnswer,
|
||||||
})
|
})
|
||||||
setContent(post.content)
|
setContent(post.content)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
Loader2,
|
Loader2,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { ForumCategory, ForumTopic } from '@/proxy/forum/forum'
|
import { ForumCategory, ForumTopic } from '@/proxy/forum/forum'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
||||||
interface TopicManagementProps {
|
interface TopicManagementProps {
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
|
|
@ -24,6 +25,7 @@ interface TopicManagementProps {
|
||||||
categoryId: string
|
categoryId: string
|
||||||
isPinned?: boolean
|
isPinned?: boolean
|
||||||
isLocked?: boolean
|
isLocked?: boolean
|
||||||
|
tenantId?: string
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>
|
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>
|
||||||
onDeleteTopic: (id: string) => Promise<void>
|
onDeleteTopic: (id: string) => Promise<void>
|
||||||
|
|
@ -49,6 +51,7 @@ export function TopicManagement({
|
||||||
onMarkTopicAsSolved,
|
onMarkTopicAsSolved,
|
||||||
onMarkTopicAsUnsolved,
|
onMarkTopicAsUnsolved,
|
||||||
}: TopicManagementProps) {
|
}: TopicManagementProps) {
|
||||||
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false)
|
const [showCreateForm, setShowCreateForm] = useState(false)
|
||||||
const [editingTopic, setEditingTopic] = useState<ForumTopic | null>(null)
|
const [editingTopic, setEditingTopic] = useState<ForumTopic | null>(null)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
|
@ -58,6 +61,7 @@ export function TopicManagement({
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isSolved: false,
|
isSolved: false,
|
||||||
|
tenantId: '',
|
||||||
})
|
})
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
|
|
@ -69,6 +73,7 @@ export function TopicManagement({
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isSolved: false,
|
isSolved: false,
|
||||||
|
tenantId: '',
|
||||||
})
|
})
|
||||||
setShowCreateForm(false)
|
setShowCreateForm(false)
|
||||||
setEditingTopic(null)
|
setEditingTopic(null)
|
||||||
|
|
@ -102,6 +107,7 @@ export function TopicManagement({
|
||||||
isPinned: topic.isPinned,
|
isPinned: topic.isPinned,
|
||||||
isLocked: topic.isLocked,
|
isLocked: topic.isLocked,
|
||||||
isSolved: topic.isSolved,
|
isSolved: topic.isSolved,
|
||||||
|
tenantId: tenant.tenantId ?? ''
|
||||||
})
|
})
|
||||||
setShowCreateForm(true)
|
setShowCreateForm(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,23 @@ import React, { useState } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import ReactQuill from 'react-quill';
|
import ReactQuill from 'react-quill';
|
||||||
import 'react-quill/dist/quill.snow.css';
|
import 'react-quill/dist/quill.snow.css';
|
||||||
|
import { useStoreState } from '@/store/store';
|
||||||
|
|
||||||
interface CreatePostModalProps {
|
interface CreatePostModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (data: { content: string }) => void;
|
onSubmit: (data: { content: string, parentPostId?: string, tenantId?: string }) => void;
|
||||||
parentPostId?: string;
|
parentPostId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostModalProps) {
|
export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostModalProps) {
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const plainText = content.replace(/<[^>]+>/g, '').trim(); // HTML etiketlerini temizle
|
const plainText = content.replace(/<[^>]+>/g, '').trim(); // HTML etiketlerini temizle
|
||||||
if (plainText) {
|
if (plainText) {
|
||||||
onSubmit({ content });
|
onSubmit({ content, parentPostId, tenantId: tenant.tenantId });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
|
import { useStoreState } from '@/store/store';
|
||||||
|
|
||||||
interface CreateTopicModalProps {
|
interface CreateTopicModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (data: { title: string; content: string }) => void;
|
onSubmit: (data: { title: string; content: string, tenantId?: string }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
|
export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (title.trim() && content.trim()) {
|
if (title.trim() && content.trim()) {
|
||||||
onSubmit({ title: title.trim(), content: content.trim() });
|
onSubmit({ title: title.trim(), content: content.trim(), tenantId: tenant.tenantId });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,6 @@ export function ForumPostCard({
|
||||||
isFirst = false,
|
isFirst = false,
|
||||||
isLiked = false,
|
isLiked = false,
|
||||||
}: PostCardProps) {
|
}: PostCardProps) {
|
||||||
const handleLike = () => {
|
|
||||||
onLike(post.id, isFirst)
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDate = (value: string | Date) => {
|
const formatDate = (value: string | Date) => {
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
if (isNaN(date.getTime())) return 'Invalid Date'
|
||||||
|
|
@ -67,7 +63,10 @@ export function ForumPostCard({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="prose prose-sm max-w-none mb-4">
|
<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>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
|
|
@ -83,13 +82,13 @@ export function ForumPostCard({
|
||||||
<span>{post.likeCount}</span>
|
<span>{post.likeCount}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
{!isFirst && (<button
|
||||||
onClick={() => onReply(post.id)}
|
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"
|
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" />
|
<Reply className="w-4 h-4" />
|
||||||
<span>Reply</span>
|
<span>Reply</span>
|
||||||
</button>
|
</button>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { buildPostTree } from './utils'
|
||||||
import { ForumPostCard } from './ForumPostCard'
|
import { ForumPostCard } from './ForumPostCard'
|
||||||
import { ForumCategoryCard } from './ForumCategoryCard'
|
import { ForumCategoryCard } from './ForumCategoryCard'
|
||||||
import { ForumTopicCard } from './ForumTopicCard'
|
import { ForumTopicCard } from './ForumTopicCard'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
||||||
interface ForumViewProps {
|
interface ForumViewProps {
|
||||||
categories: ForumCategory[]
|
categories: ForumCategory[]
|
||||||
|
|
@ -21,8 +22,14 @@ interface ForumViewProps {
|
||||||
categoryId: string
|
categoryId: string
|
||||||
isPinned?: boolean
|
isPinned?: boolean
|
||||||
isLocked?: boolean
|
isLocked?: boolean
|
||||||
|
tenantId?: string
|
||||||
|
}) => Promise<void>
|
||||||
|
onCreatePost: (post: {
|
||||||
|
topicId: string
|
||||||
|
content: string
|
||||||
|
parentPostId?: string
|
||||||
|
tenantId?: string
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise<void>
|
|
||||||
onLikePost: (id: string) => Promise<void>
|
onLikePost: (id: string) => Promise<void>
|
||||||
onUnlikePost: (id: string) => Promise<void>
|
onUnlikePost: (id: string) => Promise<void>
|
||||||
currentUserId: string
|
currentUserId: string
|
||||||
|
|
@ -68,6 +75,7 @@ export function ForumView({
|
||||||
|
|
||||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false)
|
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false)
|
||||||
const [postLikeCounts, setPostLikeCounts] = useState<Record<string, number>>({})
|
const [postLikeCounts, setPostLikeCounts] = useState<Record<string, number>>({})
|
||||||
|
const { tenant } = useStoreState((state) => state.auth)
|
||||||
|
|
||||||
const handleSearchCategorySelect = (category: ForumCategory) => {
|
const handleSearchCategorySelect = (category: ForumCategory) => {
|
||||||
if (onCategorySelect) onCategorySelect(category)
|
if (onCategorySelect) onCategorySelect(category)
|
||||||
|
|
@ -201,6 +209,7 @@ export function ForumView({
|
||||||
categoryId: selectedCategory.id,
|
categoryId: selectedCategory.id,
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
|
tenantId: tenant.tenantId,
|
||||||
})
|
})
|
||||||
setShowCreateTopic(false)
|
setShowCreateTopic(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -215,10 +224,11 @@ export function ForumView({
|
||||||
await onCreatePost({
|
await onCreatePost({
|
||||||
topicId: selectedTopic.id,
|
topicId: selectedTopic.id,
|
||||||
content: postData.content,
|
content: postData.content,
|
||||||
parentPostId: replyToPostId, // buraya dikkat
|
parentPostId: replyToPostId,
|
||||||
|
tenantId: tenant.tenantId,
|
||||||
})
|
})
|
||||||
setShowCreatePost(false)
|
setShowCreatePost(false)
|
||||||
setReplyToPostId(undefined) // temizle
|
setReplyToPostId(undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating post:', 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) => {
|
const handleReply = (postId: string) => {
|
||||||
setReplyToPostId(postId)
|
setReplyToPostId(postId)
|
||||||
setShowCreatePost(true)
|
setShowCreatePost(true)
|
||||||
|
|
@ -446,9 +449,10 @@ export function ForumView({
|
||||||
authorId: selectedTopic.authorId,
|
authorId: selectedTopic.authorId,
|
||||||
authorName: selectedTopic.authorName,
|
authorName: selectedTopic.authorName,
|
||||||
likeCount: postLikeCounts[selectedTopic.id] ?? selectedTopic.likeCount,
|
likeCount: postLikeCounts[selectedTopic.id] ?? selectedTopic.likeCount,
|
||||||
isAcceptedAnswer: false,
|
isAcceptedAnswer: false,
|
||||||
parentPostId: undefined,
|
parentPostId: undefined,
|
||||||
creationTime: selectedTopic.creationTime,
|
creationTime: selectedTopic.creationTime,
|
||||||
|
tenantId: selectedTopic.tenantId
|
||||||
}}
|
}}
|
||||||
onLike={handleLike}
|
onLike={handleLike}
|
||||||
onReply={handleReply}
|
onReply={handleReply}
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,10 @@ export function SearchModal({
|
||||||
<div className="font-medium text-gray-900 text-sm line-clamp-1">
|
<div className="font-medium text-gray-900 text-sm line-clamp-1">
|
||||||
{getTopicTitle(post.topicId)}
|
{getTopicTitle(post.topicId)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 line-clamp-2 mt-1">
|
<div
|
||||||
{post.content}
|
className="text-sm text-gray-600 line-clamp-2 mt-1"
|
||||||
</div>
|
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||||
|
></div>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
by {post.authorName} • {formatDate(post.creationTime)}
|
by {post.authorName} • {formatDate(post.creationTime)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue