Update files
This commit is contained in:
parent
48a3955c37
commit
4150c03bdb
|
|
@ -19,6 +19,8 @@ router.get('/', async (req, res) => {
|
||||||
.sort({ usageCount: -1, name: 1 })
|
.sort({ usageCount: -1, name: 1 })
|
||||||
.select('name category description usageCount');
|
.select('name category description usageCount');
|
||||||
|
|
||||||
|
console.log(`[TAGS API] Found ${tags.length} tags with status 'approved'`);
|
||||||
|
|
||||||
// Группировка по категориям
|
// Группировка по категориям
|
||||||
const grouped = {
|
const grouped = {
|
||||||
theme: [],
|
theme: [],
|
||||||
|
|
@ -33,6 +35,8 @@ router.get('/', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`[TAGS API] Grouped:`, Object.keys(grouped).map(k => `${k}: ${grouped[k].length}`).join(', '));
|
||||||
|
|
||||||
res.json({ tags: grouped, all: tags });
|
res.json({ tags: grouped, all: tags });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Ошибка получения тегов', error);
|
logError('Ошибка получения тегов', error);
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,20 @@ async function initTags() {
|
||||||
for (const tagData of INITIAL_TAGS) {
|
for (const tagData of INITIAL_TAGS) {
|
||||||
const existing = await Tag.findOne({ name: tagData.name });
|
const existing = await Tag.findOne({ name: tagData.name });
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
await Tag.create(tagData);
|
// Явно устанавливаем статус
|
||||||
|
await Tag.create({
|
||||||
|
...tagData,
|
||||||
|
status: 'approved' // Явно устанавливаем статус
|
||||||
|
});
|
||||||
created++;
|
created++;
|
||||||
console.log(`✅ Создан тег: ${tagData.name}`);
|
console.log(`✅ Создан тег: ${tagData.name}`);
|
||||||
} else {
|
} else {
|
||||||
|
// Обновляем статус существующего тега, если он не approved
|
||||||
|
if (existing.status !== 'approved') {
|
||||||
|
existing.status = 'approved';
|
||||||
|
await existing.save();
|
||||||
|
console.log(`✅ Обновлен статус тега: ${tagData.name}`);
|
||||||
|
}
|
||||||
skipped++;
|
skipped++;
|
||||||
console.log(`⏭️ Тег уже существует: ${tagData.name}`);
|
console.log(`⏭️ Тег уже существует: ${tagData.name}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,15 @@
|
||||||
.empty-state p {
|
.empty-state p {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state-hint {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.7;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
|
|
@ -124,6 +133,10 @@
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .btn-primary {
|
[data-theme="dark"] .btn-primary {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams, useNavigate } from 'react-router-dom'
|
||||||
import { getPosts } from '../utils/api'
|
import { getPosts } from '../utils/api'
|
||||||
import PostCard from '../components/PostCard'
|
import PostCard from '../components/PostCard'
|
||||||
import CreatePostModal from '../components/CreatePostModal'
|
import CreatePostModal from '../components/CreatePostModal'
|
||||||
import { Plus } from 'lucide-react'
|
import { Plus, Settings } from 'lucide-react'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import './Feed.css'
|
import './Feed.css'
|
||||||
|
|
||||||
export default function Feed({ user }) {
|
export default function Feed({ user }) {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
const [posts, setPosts] = useState([])
|
const [posts, setPosts] = useState([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
|
|
@ -153,10 +154,41 @@ export default function Feed({ user }) {
|
||||||
</div>
|
</div>
|
||||||
) : posts.length === 0 ? (
|
) : posts.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<p>Пока нет постов</p>
|
{filter === 'interests' ? (
|
||||||
<button className="btn-primary" onClick={handleCreatePost}>
|
<>
|
||||||
Создать первый пост
|
<p>Нет постов по вашим интересам</p>
|
||||||
</button>
|
<p className="empty-state-hint">
|
||||||
|
Проверьте выбранные теги в настройках профиля
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
hapticFeedback('light')
|
||||||
|
navigate('/profile')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Settings size={18} style={{ marginRight: '8px' }} />
|
||||||
|
Открыть настройки
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : filter === 'following' ? (
|
||||||
|
<>
|
||||||
|
<p>Нет постов от подписок</p>
|
||||||
|
<p className="empty-state-hint">
|
||||||
|
Подпишитесь на пользователей, чтобы видеть их посты здесь
|
||||||
|
</p>
|
||||||
|
<button className="btn-primary" onClick={handleCreatePost}>
|
||||||
|
Создать пост
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>Пока нет постов</p>
|
||||||
|
<button className="btn-primary" onClick={handleCreatePost}>
|
||||||
|
Создать первый пост
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,20 @@
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-tags-message {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tags-message code {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Модалка с информацией о теге */
|
/* Модалка с информацией о теге */
|
||||||
.tag-info-modal {
|
.tag-info-modal {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ export default function Profile({ user, setUser }) {
|
||||||
try {
|
try {
|
||||||
setLoadingTags(true)
|
setLoadingTags(true)
|
||||||
const data = await getTags()
|
const data = await getTags()
|
||||||
|
console.log('[Profile] Tags data received:', data)
|
||||||
|
console.log('[Profile] Tags grouped:', data.tags)
|
||||||
|
console.log('[Profile] Tags all:', data.all)
|
||||||
setAllTags(data.tags || { theme: [], style: [], mood: [], technical: [] })
|
setAllTags(data.tags || { theme: [], style: [], mood: [], technical: [] })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки тегов:', error)
|
console.error('Ошибка загрузки тегов:', error)
|
||||||
|
|
@ -537,57 +540,72 @@ export default function Profile({ user, setUser }) {
|
||||||
<div className="spinner" />
|
<div className="spinner" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
Object.entries(TAG_CATEGORIES).map(([categoryKey, categoryName]) => {
|
(() => {
|
||||||
const categoryTags = allTags[categoryKey] || []
|
const hasAnyTags = Object.values(allTags).some(tags => tags.length > 0)
|
||||||
if (categoryTags.length === 0) return null
|
|
||||||
|
if (!hasAnyTags) {
|
||||||
return (
|
return (
|
||||||
<div key={categoryKey} className="tag-category-section">
|
<div className="empty-tags-message">
|
||||||
<h3 className="tag-category-title">{categoryName}</h3>
|
<p>Теги не найдены. Убедитесь, что скрипт инициализации тегов был запущен.</p>
|
||||||
<div className="tags-grid">
|
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '8px' }}>
|
||||||
{categoryTags.map(tag => {
|
Запустите: <code>node backend/scripts/initTags.js</code>
|
||||||
const isSelected = selectedTags.includes(tag.name)
|
</p>
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={tag.name}
|
|
||||||
className={`tag-preference-item ${isSelected ? 'selected' : ''}`}
|
|
||||||
onClick={() => toggleTag(tag.name)}
|
|
||||||
>
|
|
||||||
<div className="tag-preference-content">
|
|
||||||
<span className="tag-preference-name">{tag.name}</span>
|
|
||||||
{tag.description && (
|
|
||||||
<div
|
|
||||||
className="tag-preference-description"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
setShowTagInfo({
|
|
||||||
name: tag.name,
|
|
||||||
description: tag.description,
|
|
||||||
category: TAG_CATEGORIES[tag.category] || tag.category
|
|
||||||
})
|
|
||||||
hapticFeedback('light')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Info size={12} />
|
|
||||||
<span>Нажмите для описания</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="tag-preference-checkbox">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={isSelected}
|
|
||||||
onChange={() => toggleTag(tag.name)}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
})
|
|
||||||
|
return Object.entries(TAG_CATEGORIES).map(([categoryKey, categoryName]) => {
|
||||||
|
const categoryTags = allTags[categoryKey] || []
|
||||||
|
if (categoryTags.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={categoryKey} className="tag-category-section">
|
||||||
|
<h3 className="tag-category-title">{categoryName}</h3>
|
||||||
|
<div className="tags-grid">
|
||||||
|
{categoryTags.map(tag => {
|
||||||
|
const isSelected = selectedTags.includes(tag.name)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={tag.name}
|
||||||
|
className={`tag-preference-item ${isSelected ? 'selected' : ''}`}
|
||||||
|
onClick={() => toggleTag(tag.name)}
|
||||||
|
>
|
||||||
|
<div className="tag-preference-content">
|
||||||
|
<span className="tag-preference-name">{tag.name}</span>
|
||||||
|
{tag.description && (
|
||||||
|
<div
|
||||||
|
className="tag-preference-description"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setShowTagInfo({
|
||||||
|
name: tag.name,
|
||||||
|
description: tag.description,
|
||||||
|
category: TAG_CATEGORIES[tag.category] || tag.category
|
||||||
|
})
|
||||||
|
hapticFeedback('light')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Info size={12} />
|
||||||
|
<span>Нажмите для описания</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="tag-preference-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={() => toggleTag(tag.name)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})()
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTags.length > 0 && (
|
{selectedTags.length > 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue