Update files
This commit is contained in:
parent
0305e87ce5
commit
63ef5c3a95
|
|
@ -150,6 +150,16 @@ router.put('/preferences', authenticate, async (req, res) => {
|
||||||
return res.status(400).json({ error: 'Слишком много тегов' });
|
return res.status(400).json({ error: 'Слишком много тегов' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверка формата каждого тега (только буквы, цифры, подчеркивания и дефисы)
|
||||||
|
for (const tag of normalizedTags) {
|
||||||
|
if (tag.length > 50) {
|
||||||
|
return res.status(400).json({ error: `Тег "${tag}" слишком длинный (максимум 50 символов)` });
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z0-9_\-]+$/.test(tag)) {
|
||||||
|
return res.status(400).json({ error: `Тег "${tag}" содержит недопустимые символы. Разрешены только буквы, цифры, подчеркивания и дефисы` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await User.findByIdAndUpdate(req.user._id, {
|
await User.findByIdAndUpdate(req.user._id, {
|
||||||
preferredTags: normalizedTags
|
preferredTags: normalizedTags
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const INITIAL_TAGS = [
|
||||||
// Тематика
|
// Тематика
|
||||||
{ name: 'furry', category: 'theme', description: 'Furry контент' },
|
{ name: 'furry', category: 'theme', description: 'Furry контент' },
|
||||||
{ name: 'anime', category: 'theme', description: 'Аниме контент' },
|
{ name: 'anime', category: 'theme', description: 'Аниме контент' },
|
||||||
|
{ name: 'other', category: 'theme', description: 'Другое' },
|
||||||
{ name: 'sci-fi', category: 'theme', description: 'Научная фантастика' },
|
{ name: 'sci-fi', category: 'theme', description: 'Научная фантастика' },
|
||||||
{ name: 'fantasy', category: 'theme', description: 'Фэнтези' },
|
{ name: 'fantasy', category: 'theme', description: 'Фэнтези' },
|
||||||
{ name: 'irl', category: 'theme', description: 'Реальный мир' },
|
{ name: 'irl', category: 'theme', description: 'Реальный мир' },
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,16 @@
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-empty {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-empty:hover,
|
||||||
|
.tag-suggestion-empty:active {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-suggestion-name {
|
.tag-suggestion-name {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,24 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI
|
||||||
setExternalImages(prev => prev.filter((_, i) => i !== index))
|
setExternalImages(prev => prev.filter((_, i) => i !== index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTagInputChange = (e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
// На мобильных устройствах пробел может быть добавлен до keyDown
|
||||||
|
// Обрабатываем пробел в onChange
|
||||||
|
if (value.endsWith(' ')) {
|
||||||
|
const trimmed = value.trim().toLowerCase()
|
||||||
|
if (trimmed && !selectedTags.includes(trimmed)) {
|
||||||
|
addTag(trimmed)
|
||||||
|
return // Не обновляем tagInput, addTag уже очистит его
|
||||||
|
} else {
|
||||||
|
// Если тег уже добавлен или пустой, просто очищаем пробел
|
||||||
|
setTagInput(value.trim())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTagInput(value)
|
||||||
|
}
|
||||||
|
|
||||||
const handleTagInputKeyDown = (e) => {
|
const handleTagInputKeyDown = (e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
@ -116,11 +134,30 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTag = (tag) => {
|
const addTag = (tag) => {
|
||||||
if (tag && !selectedTags.includes(tag) && selectedTags.length < 20) {
|
// Нормализуем тег: убираем пробелы, приводим к нижнему регистру
|
||||||
setSelectedTags([...selectedTags, tag])
|
const normalizedTag = tag.trim().toLowerCase()
|
||||||
|
|
||||||
|
// Проверяем формат тега (только буквы, цифры, подчеркивания и дефисы)
|
||||||
|
if (!normalizedTag || normalizedTag.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем формат тега на соответствие валидации бэкенда
|
||||||
|
if (!/^[a-zA-Z0-9_\-]+$/.test(normalizedTag)) {
|
||||||
|
// Если тег содержит недопустимые символы, показываем предупреждение
|
||||||
|
alert('Тег может содержать только буквы, цифры, подчеркивания и дефисы')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedTags.includes(normalizedTag) && selectedTags.length < 20) {
|
||||||
|
setSelectedTags([...selectedTags, normalizedTag])
|
||||||
setTagInput('')
|
setTagInput('')
|
||||||
setShowTagSuggestions(false)
|
setShowTagSuggestions(false)
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
|
} else if (selectedTags.includes(normalizedTag)) {
|
||||||
|
// Если тег уже добавлен, просто очищаем поле ввода
|
||||||
|
setTagInput('')
|
||||||
|
setShowTagSuggestions(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +317,7 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI
|
||||||
className="tag-input"
|
className="tag-input"
|
||||||
placeholder="Введите тег и нажмите Enter или пробел"
|
placeholder="Введите тег и нажмите Enter или пробел"
|
||||||
value={tagInput}
|
value={tagInput}
|
||||||
onChange={e => setTagInput(e.target.value)}
|
onChange={handleTagInputChange}
|
||||||
onKeyDown={handleTagInputKeyDown}
|
onKeyDown={handleTagInputKeyDown}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
if (tagInput.trim().length > 0) {
|
if (tagInput.trim().length > 0) {
|
||||||
|
|
@ -290,25 +327,36 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Подсказки тегов */}
|
{/* Подсказки тегов */}
|
||||||
{showTagSuggestions && tagSuggestions.length > 0 && (
|
{showTagSuggestions && (
|
||||||
<div className="tag-suggestions" ref={tagSuggestionsRef}>
|
<div className="tag-suggestions" ref={tagSuggestionsRef}>
|
||||||
{tagSuggestions.map(tag => (
|
{tagSuggestions.length > 0 ? (
|
||||||
<div
|
tagSuggestions.map(tag => (
|
||||||
key={tag.name}
|
<div
|
||||||
className="tag-suggestion-item"
|
key={tag.name}
|
||||||
onClick={() => handleTagSuggestionClick(tag)}
|
className="tag-suggestion-item"
|
||||||
>
|
onClick={() => handleTagSuggestionClick(tag)}
|
||||||
<div className="tag-suggestion-name">{tag.name}</div>
|
>
|
||||||
{tag.category && (
|
<div className="tag-suggestion-name">{tag.name}</div>
|
||||||
<div className="tag-suggestion-category">
|
{tag.category && (
|
||||||
{TAG_CATEGORIES[tag.category] || tag.category}
|
<div className="tag-suggestion-category">
|
||||||
|
{TAG_CATEGORIES[tag.category] || tag.category}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tag.description && (
|
||||||
|
<div className="tag-suggestion-description">{tag.description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
tagInput.trim().length > 0 && (
|
||||||
|
<div className="tag-suggestion-item tag-suggestion-empty">
|
||||||
|
<div className="tag-suggestion-name">Тег "{tagInput.trim()}" не найден</div>
|
||||||
|
<div className="tag-suggestion-description">
|
||||||
|
Нажмите Enter или пробел, чтобы добавить этот тег
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
{tag.description && (
|
)
|
||||||
<div className="tag-suggestion-description">{tag.description}</div>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -742,3 +742,97 @@
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Поле ввода тегов в профиле */
|
||||||
|
.tag-input-section {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--button-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestions {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-item:hover,
|
||||||
|
.tag-suggestion-item:active {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-empty {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-empty:hover,
|
||||||
|
.tag-suggestion-empty:active {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-category {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-suggestion-description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { Settings, Heart, Edit2, Shield, UserPlus, Copy, Info, Tag, X } from 'lucide-react'
|
import { Settings, Heart, Edit2, Shield, UserPlus, Copy, Info, Tag, X } from 'lucide-react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { updateProfile, getTags, getPreferredTags, updatePreferredTags } from '../utils/api'
|
import { updateProfile, getTags, getPreferredTags, updatePreferredTags, autocompleteTags } from '../utils/api'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import ThemeToggle from '../components/ThemeToggle'
|
import ThemeToggle from '../components/ThemeToggle'
|
||||||
import FollowListModal from '../components/FollowListModal'
|
import FollowListModal from '../components/FollowListModal'
|
||||||
|
|
@ -57,6 +57,11 @@ export default function Profile({ user, setUser }) {
|
||||||
const [selectedTags, setSelectedTags] = useState([])
|
const [selectedTags, setSelectedTags] = useState([])
|
||||||
const [loadingTags, setLoadingTags] = useState(false)
|
const [loadingTags, setLoadingTags] = useState(false)
|
||||||
const [showTagInfo, setShowTagInfo] = useState(null) // { name, description, category }
|
const [showTagInfo, setShowTagInfo] = useState(null) // { name, description, category }
|
||||||
|
const [tagInput, setTagInput] = useState('')
|
||||||
|
const [tagSuggestions, setTagSuggestions] = useState([])
|
||||||
|
const [showTagSuggestions, setShowTagSuggestions] = useState(false)
|
||||||
|
const tagInputRef = useRef(null)
|
||||||
|
const tagSuggestionsRef = useRef(null)
|
||||||
|
|
||||||
const handleSaveBio = async () => {
|
const handleSaveBio = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -116,9 +121,53 @@ export default function Profile({ user, setUser }) {
|
||||||
} else {
|
} else {
|
||||||
loadPreferredTags()
|
loadPreferredTags()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Сбросить поле ввода при закрытии модалки
|
||||||
|
setTagInput('')
|
||||||
|
setTagSuggestions([])
|
||||||
|
setShowTagSuggestions(false)
|
||||||
}
|
}
|
||||||
}, [showTagPreferences])
|
}, [showTagPreferences])
|
||||||
|
|
||||||
|
// Автодополнение тегов
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTagSuggestions = async () => {
|
||||||
|
if (tagInput.trim().length > 0) {
|
||||||
|
try {
|
||||||
|
const data = await autocompleteTags(tagInput.trim())
|
||||||
|
setTagSuggestions(data.tags || [])
|
||||||
|
setShowTagSuggestions(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка автодополнения тегов:', error)
|
||||||
|
setTagSuggestions([])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTagSuggestions([])
|
||||||
|
setShowTagSuggestions(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debounceTimer = setTimeout(fetchTagSuggestions, 300)
|
||||||
|
return () => clearTimeout(debounceTimer)
|
||||||
|
}, [tagInput])
|
||||||
|
|
||||||
|
// Закрыть подсказки при клике вне
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (
|
||||||
|
tagSuggestionsRef.current &&
|
||||||
|
!tagSuggestionsRef.current.contains(event.target) &&
|
||||||
|
tagInputRef.current &&
|
||||||
|
!tagInputRef.current.contains(event.target)
|
||||||
|
) {
|
||||||
|
setShowTagSuggestions(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const loadTags = async () => {
|
const loadTags = async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingTags(true)
|
setLoadingTags(true)
|
||||||
|
|
@ -145,13 +194,81 @@ export default function Profile({ user, setUser }) {
|
||||||
|
|
||||||
const toggleTag = (tagName) => {
|
const toggleTag = (tagName) => {
|
||||||
hapticFeedback('light')
|
hapticFeedback('light')
|
||||||
if (selectedTags.includes(tagName)) {
|
const normalizedTag = tagName.toLowerCase().trim()
|
||||||
setSelectedTags(selectedTags.filter(t => t !== tagName))
|
if (selectedTags.includes(normalizedTag)) {
|
||||||
|
setSelectedTags(selectedTags.filter(t => t !== normalizedTag))
|
||||||
} else {
|
} else {
|
||||||
setSelectedTags([...selectedTags, tagName])
|
setSelectedTags([...selectedTags, normalizedTag])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTagInputChange = (e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
// На мобильных устройствах пробел может быть добавлен до keyDown
|
||||||
|
// Обрабатываем пробел в onChange
|
||||||
|
if (value.endsWith(' ')) {
|
||||||
|
const trimmed = value.trim().toLowerCase()
|
||||||
|
if (trimmed && !selectedTags.includes(trimmed)) {
|
||||||
|
addTag(trimmed)
|
||||||
|
return // Не обновляем tagInput, addTag уже очистит его
|
||||||
|
} else {
|
||||||
|
// Если тег уже добавлен или пустой, просто очищаем пробел
|
||||||
|
setTagInput(value.trim())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTagInput(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTagInputKeyDown = (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
const trimmed = tagInput.trim().toLowerCase()
|
||||||
|
if (trimmed && !selectedTags.includes(trimmed)) {
|
||||||
|
addTag(trimmed)
|
||||||
|
}
|
||||||
|
} else if (e.key === 'Backspace' && tagInput === '' && selectedTags.length > 0) {
|
||||||
|
removeTag(selectedTags[selectedTags.length - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addTag = (tag) => {
|
||||||
|
// Нормализуем тег: убираем пробелы, приводим к нижнему регистру
|
||||||
|
const normalizedTag = tag.trim().toLowerCase()
|
||||||
|
|
||||||
|
// Проверяем формат тега (только буквы, цифры, подчеркивания и дефисы)
|
||||||
|
if (!normalizedTag || normalizedTag.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем формат тега на соответствие валидации бэкенда
|
||||||
|
if (!/^[a-zA-Z0-9_\-]+$/.test(normalizedTag)) {
|
||||||
|
// Если тег содержит недопустимые символы, показываем предупреждение
|
||||||
|
alert('Тег может содержать только буквы, цифры, подчеркивания и дефисы')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedTags.includes(normalizedTag) && selectedTags.length < 50) {
|
||||||
|
setSelectedTags([...selectedTags, normalizedTag])
|
||||||
|
setTagInput('')
|
||||||
|
setShowTagSuggestions(false)
|
||||||
|
hapticFeedback('light')
|
||||||
|
} else if (selectedTags.includes(normalizedTag)) {
|
||||||
|
// Если тег уже добавлен, просто очищаем поле ввода
|
||||||
|
setTagInput('')
|
||||||
|
setShowTagSuggestions(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTag = (tag) => {
|
||||||
|
setSelectedTags(selectedTags.filter(t => t !== tag))
|
||||||
|
hapticFeedback('light')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTagSuggestionClick = (tag) => {
|
||||||
|
addTag(tag.name)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSaveTagPreferences = async () => {
|
const handleSaveTagPreferences = async () => {
|
||||||
try {
|
try {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
@ -608,6 +725,62 @@ export default function Profile({ user, setUser }) {
|
||||||
})()
|
})()
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Поле ввода для ручного добавления тегов */}
|
||||||
|
<div className="tag-input-section">
|
||||||
|
<div className="tag-input-wrapper" ref={tagInputRef}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="tag-input"
|
||||||
|
placeholder="Введите тег и нажмите Enter или пробел"
|
||||||
|
value={tagInput}
|
||||||
|
onChange={handleTagInputChange}
|
||||||
|
onKeyDown={handleTagInputKeyDown}
|
||||||
|
onFocus={() => {
|
||||||
|
if (tagInput.trim().length > 0) {
|
||||||
|
setShowTagSuggestions(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Подсказки тегов */}
|
||||||
|
{showTagSuggestions && (
|
||||||
|
<div className="tag-suggestions" ref={tagSuggestionsRef}>
|
||||||
|
{tagSuggestions.length > 0 ? (
|
||||||
|
tagSuggestions.map(tag => (
|
||||||
|
<div
|
||||||
|
key={tag.name}
|
||||||
|
className="tag-suggestion-item"
|
||||||
|
onClick={() => handleTagSuggestionClick(tag)}
|
||||||
|
>
|
||||||
|
<div className="tag-suggestion-name">{tag.name}</div>
|
||||||
|
{tag.category && (
|
||||||
|
<div className="tag-suggestion-category">
|
||||||
|
{TAG_CATEGORIES[tag.category] || tag.category}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tag.description && (
|
||||||
|
<div className="tag-suggestion-description">{tag.description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
tagInput.trim().length > 0 && (
|
||||||
|
<div className="tag-suggestion-item tag-suggestion-empty">
|
||||||
|
<div className="tag-suggestion-name">Тег "{tagInput.trim()}" не найден</div>
|
||||||
|
<div className="tag-suggestion-description">
|
||||||
|
Нажмите Enter или пробел, чтобы добавить этот тег
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="tag-input-hint">
|
||||||
|
Вы можете добавить любой тег вручную, даже если его нет в списке выше
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{selectedTags.length > 0 && (
|
{selectedTags.length > 0 && (
|
||||||
<div className="selected-tags-summary">
|
<div className="selected-tags-summary">
|
||||||
<strong>Выбрано: {selectedTags.length}</strong>
|
<strong>Выбрано: {selectedTags.length}</strong>
|
||||||
|
|
@ -615,7 +788,7 @@ export default function Profile({ user, setUser }) {
|
||||||
{selectedTags.map(tag => (
|
{selectedTags.map(tag => (
|
||||||
<span key={tag} className="selected-tag-chip">
|
<span key={tag} className="selected-tag-chip">
|
||||||
{tag}
|
{tag}
|
||||||
<button onClick={() => toggleTag(tag)} className="tag-chip-remove">
|
<button onClick={() => removeTag(tag)} className="tag-chip-remove">
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue