feat: добавлено автозаполнение полей wishlist из ссылки (v3.9.0)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m5s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m5s
- Добавлен эндпоинт /api/wishlist/metadata для извлечения метаданных из URL - Реализовано извлечение Open Graph тегов (title, image, description) - Добавлена кнопка Pull для ручной загрузки информации из ссылки - Автоматическое заполнение полей: название, цена, картинка - Обновлена версия до 3.9.0
This commit is contained in:
@@ -10,6 +10,9 @@ import TestWords from './components/TestWords'
|
||||
import Profile from './components/Profile'
|
||||
import TaskList from './components/TaskList'
|
||||
import TaskForm from './components/TaskForm.jsx'
|
||||
import Wishlist from './components/Wishlist'
|
||||
import WishlistForm from './components/WishlistForm'
|
||||
import WishlistDetail from './components/WishlistDetail'
|
||||
import TodoistIntegration from './components/TodoistIntegration'
|
||||
import TelegramIntegration from './components/TelegramIntegration'
|
||||
import { AuthProvider, useAuth } from './components/auth/AuthContext'
|
||||
@@ -21,8 +24,8 @@ const CURRENT_WEEK_API_URL = '/playlife-feed'
|
||||
const FULL_STATISTICS_API_URL = '/d2dc349a-0d13-49b2-a8f0-1ab094bfba9b'
|
||||
|
||||
// Определяем основные табы (без крестика) и глубокие табы (с крестиком)
|
||||
const mainTabs = ['current', 'test-config', 'tasks', 'profile']
|
||||
const deepTabs = ['add-words', 'add-config', 'test', 'task-form', 'words', 'todoist-integration', 'telegram-integration', 'full', 'priorities']
|
||||
const mainTabs = ['current', 'test-config', 'tasks', 'wishlist', 'profile']
|
||||
const deepTabs = ['add-words', 'add-config', 'test', 'task-form', 'wishlist-form', 'wishlist-detail', 'words', 'todoist-integration', 'telegram-integration', 'full', 'priorities']
|
||||
|
||||
function AppContent() {
|
||||
const { authFetch, isAuthenticated, loading: authLoading } = useAuth()
|
||||
@@ -53,6 +56,9 @@ function AppContent() {
|
||||
test: false,
|
||||
tasks: false,
|
||||
'task-form': false,
|
||||
wishlist: false,
|
||||
'wishlist-form': false,
|
||||
'wishlist-detail': false,
|
||||
profile: false,
|
||||
'todoist-integration': false,
|
||||
'telegram-integration': false,
|
||||
@@ -70,6 +76,9 @@ function AppContent() {
|
||||
test: false,
|
||||
tasks: false,
|
||||
'task-form': false,
|
||||
wishlist: false,
|
||||
'wishlist-form': false,
|
||||
'wishlist-detail': false,
|
||||
profile: false,
|
||||
'todoist-integration': false,
|
||||
'telegram-integration': false,
|
||||
@@ -106,6 +115,7 @@ function AppContent() {
|
||||
const [prioritiesRefreshTrigger, setPrioritiesRefreshTrigger] = useState(0)
|
||||
const [testConfigRefreshTrigger, setTestConfigRefreshTrigger] = useState(0)
|
||||
const [wordsRefreshTrigger, setWordsRefreshTrigger] = useState(0)
|
||||
const [wishlistRefreshTrigger, setWishlistRefreshTrigger] = useState(0)
|
||||
|
||||
// Восстанавливаем последний выбранный таб после перезагрузки
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
@@ -118,7 +128,7 @@ function AppContent() {
|
||||
// Проверяем URL только для глубоких табов
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const tabFromUrl = urlParams.get('tab')
|
||||
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'test-config', 'add-config', 'test', 'tasks', 'task-form', 'profile', 'todoist-integration', 'telegram-integration']
|
||||
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'test-config', 'add-config', 'test', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'profile', 'todoist-integration', 'telegram-integration']
|
||||
|
||||
if (tabFromUrl && validTabs.includes(tabFromUrl) && deepTabs.includes(tabFromUrl)) {
|
||||
// Если в URL есть глубокий таб, восстанавливаем его
|
||||
@@ -492,7 +502,7 @@ function AppContent() {
|
||||
// Обработчик кнопки "назад" в браузере (только для глубоких табов)
|
||||
useEffect(() => {
|
||||
const handlePopState = (event) => {
|
||||
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'test-config', 'add-config', 'test', 'tasks', 'task-form', 'profile', 'todoist-integration', 'telegram-integration']
|
||||
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'test-config', 'add-config', 'test', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'profile', 'todoist-integration', 'telegram-integration']
|
||||
|
||||
// Проверяем state текущей записи истории (куда мы вернулись)
|
||||
if (event.state && event.state.tab) {
|
||||
@@ -597,8 +607,8 @@ function AppContent() {
|
||||
setSelectedProject(null)
|
||||
setTabParams({})
|
||||
updateUrl('full', {}, activeTab)
|
||||
} else if (tab !== activeTab || tab === 'task-form') {
|
||||
// Для task-form всегда обновляем параметры, даже если это тот же таб
|
||||
} else if (tab !== activeTab || tab === 'task-form' || tab === 'wishlist-form') {
|
||||
// Для task-form и wishlist-form всегда обновляем параметры, даже если это тот же таб
|
||||
markTabAsLoaded(tab)
|
||||
|
||||
// Определяем, является ли текущий таб глубоким
|
||||
@@ -616,8 +626,9 @@ function AppContent() {
|
||||
updateUrl(tab, {}, activeTab)
|
||||
}
|
||||
} else {
|
||||
// Для task-form явно удаляем taskId, если он undefined
|
||||
if (tab === 'task-form' && params.taskId === undefined) {
|
||||
// Для task-form и wishlist-form явно удаляем параметры, если они undefined
|
||||
if ((tab === 'task-form' && params.taskId === undefined) ||
|
||||
(tab === 'wishlist-form' && params.wishlistId === undefined)) {
|
||||
setTabParams({})
|
||||
if (isNewTabMain) {
|
||||
clearUrl()
|
||||
@@ -653,6 +664,10 @@ function AppContent() {
|
||||
if (activeTab === 'task-form' && tab === 'tasks') {
|
||||
fetchTasksData(true)
|
||||
}
|
||||
// Обновляем список желаний при возврате из экрана редактирования
|
||||
if (activeTab === 'wishlist-form' && tab === 'wishlist') {
|
||||
setWishlistRefreshTrigger(prev => prev + 1)
|
||||
}
|
||||
// Загрузка данных произойдет в useEffect при изменении activeTab
|
||||
}
|
||||
}
|
||||
@@ -705,7 +720,7 @@ function AppContent() {
|
||||
}, [activeTab])
|
||||
|
||||
// Определяем, нужно ли скрывать нижнюю панель (для fullscreen экранов)
|
||||
const isFullscreenTab = activeTab === 'test' || activeTab === 'add-words' || activeTab === 'add-config' || activeTab === 'task-form' || activeTab === 'todoist-integration' || activeTab === 'telegram-integration' || activeTab === 'full' || activeTab === 'priorities'
|
||||
const isFullscreenTab = activeTab === 'test' || activeTab === 'add-words' || activeTab === 'add-config' || activeTab === 'task-form' || activeTab === 'wishlist-form' || activeTab === 'wishlist-detail' || activeTab === 'todoist-integration' || activeTab === 'telegram-integration' || activeTab === 'full' || activeTab === 'priorities'
|
||||
|
||||
// Определяем отступы для контейнера
|
||||
const getContainerPadding = () => {
|
||||
@@ -854,6 +869,36 @@ function AppContent() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loadedTabs.wishlist && (
|
||||
<div className={activeTab === 'wishlist' ? 'block' : 'hidden'}>
|
||||
<Wishlist
|
||||
onNavigate={handleNavigate}
|
||||
refreshTrigger={wishlistRefreshTrigger}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loadedTabs['wishlist-form'] && (
|
||||
<div className={activeTab === 'wishlist-form' ? 'block' : 'hidden'}>
|
||||
<WishlistForm
|
||||
key={tabParams.wishlistId || 'new'}
|
||||
onNavigate={handleNavigate}
|
||||
wishlistId={tabParams.wishlistId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loadedTabs['wishlist-detail'] && (
|
||||
<div className={activeTab === 'wishlist-detail' ? 'block' : 'hidden'}>
|
||||
<WishlistDetail
|
||||
key={tabParams.wishlistId}
|
||||
onNavigate={handleNavigate}
|
||||
wishlistId={tabParams.wishlistId}
|
||||
onRefresh={() => setWishlistRefreshTrigger(prev => prev + 1)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loadedTabs.profile && (
|
||||
<div className={activeTab === 'profile' ? 'block' : 'hidden'}>
|
||||
<Profile onNavigate={handleNavigate} />
|
||||
@@ -938,6 +983,28 @@ function AppContent() {
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-indigo-500 to-purple-500"></div>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange('wishlist')}
|
||||
className={`min-w-max whitespace-nowrap px-4 py-4 transition-all duration-300 relative ${
|
||||
activeTab === 'wishlist' || activeTab === 'wishlist-form'
|
||||
? 'text-indigo-700 bg-white/50'
|
||||
: 'text-gray-600 hover:text-indigo-600 hover:bg-white/30'
|
||||
}`}
|
||||
title="Желания"
|
||||
>
|
||||
<span className="relative z-10 flex items-center justify-center">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="20 12 20 22 4 22 4 12"></polyline>
|
||||
<rect x="2" y="7" width="20" height="5"></rect>
|
||||
<line x1="12" y1="22" x2="12" y2="7"></line>
|
||||
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
|
||||
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{(activeTab === 'wishlist' || activeTab === 'wishlist-form') && (
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-indigo-500 to-purple-500"></div>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange('profile')}
|
||||
className={`min-w-max whitespace-nowrap px-4 py-4 transition-all duration-300 relative ${
|
||||
|
||||
Reference in New Issue
Block a user