From 925abca6eab3f752f6f6bde7e867498b58d46087 Mon Sep 17 00:00:00 2001
From: "Andrey K. Choi"
Date: Sun, 9 Nov 2025 13:22:56 +0900
Subject: [PATCH] Fix expand button display in Magazine, Masonry and Timeline
layouts
- Fixed data mapping for ExpandableGroup components in all three layouts
- Increased initialShowCount for magazine layout (8 for first, 6 for others)
- Fixed overlay settings to use link_overlay instead of group_overlay in masonry layout
- Added debug logging to ExpandableGroup to help diagnose issues
- All layouts now properly convert group.links to ExpandableGroup format with icon mapping
Issues fixed:
- Timeline layout: Now properly maps link data with icons
- Magazine layout: Increased link count and fixed data mapping
- Masonry layout: Fixed overlay settings and data mapping
---
create_test_data.py | 323 ++++++++++++++++++
.../DEPLOYMENT_INSTRUCTIONS.md | 0
.../TEMPLATES_AND_GROUPS_REPORT.md | 0
.../src/app/[username]/page.tsx | 30 +-
.../src/app/components/ExpandableGroup.tsx | 6 +
5 files changed, 353 insertions(+), 6 deletions(-)
create mode 100644 create_test_data.py
rename DEPLOYMENT_INSTRUCTIONS.md => docs/DEPLOYMENT_INSTRUCTIONS.md (100%)
rename TEMPLATES_AND_GROUPS_REPORT.md => docs/TEMPLATES_AND_GROUPS_REPORT.md (100%)
diff --git a/create_test_data.py b/create_test_data.py
new file mode 100644
index 0000000..91129b4
--- /dev/null
+++ b/create_test_data.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python3
+"""
+Скрипт для создания тестовых групп и ссылок через API
+"""
+
+import requests
+import json
+import random
+import time
+
+API_BASE = "https://links.shareon.kr/api"
+# Конфигурация - попробуем разные варианты
+API_ENDPOINTS = [
+
+ "https://links.shareon.kr/api",
+
+]
+USERNAME = "trevor"
+PASSWORD = "Cl0ud1985!"
+
+# Данные для групп и ссылок
+GROUPS_DATA = [
+ {
+ "name": "Социальные сети",
+ "description": "Популярные социальные платформы и мессенджеры",
+ "links": [
+ {"title": "Facebook", "url": "https://facebook.com", "description": "Социальная сеть Facebook"},
+ {"title": "Instagram", "url": "https://instagram.com", "description": "Фото и видео платформа"},
+ {"title": "Twitter", "url": "https://twitter.com", "description": "Микроблоггинг платформа"},
+ {"title": "LinkedIn", "url": "https://linkedin.com", "description": "Профессиональная сеть"},
+ {"title": "YouTube", "url": "https://youtube.com", "description": "Видеохостинг"},
+ {"title": "TikTok", "url": "https://tiktok.com", "description": "Короткие видео"},
+ {"title": "WhatsApp", "url": "https://whatsapp.com", "description": "Мессенджер"},
+ {"title": "Telegram", "url": "https://telegram.org", "description": "Облачный мессенджер"},
+ {"title": "Discord", "url": "https://discord.com", "description": "Голосовой чат для геймеров"},
+ {"title": "Reddit", "url": "https://reddit.com", "description": "Социальные новости"},
+ {"title": "Pinterest", "url": "https://pinterest.com", "description": "Визуальные идеи"},
+ {"title": "Snapchat", "url": "https://snapchat.com", "description": "Мгновенные фото"},
+ {"title": "Clubhouse", "url": "https://clubhouse.com", "description": "Голосовые чаты"},
+ {"title": "VKontakte", "url": "https://vk.com", "description": "Российская соцсеть"},
+ {"title": "Odnoklassniki", "url": "https://ok.ru", "description": "Социальная сеть"}
+ ]
+ },
+ {
+ "name": "Разработка и IT",
+ "description": "Инструменты и ресурсы для программистов",
+ "links": [
+ {"title": "GitHub", "url": "https://github.com", "description": "Хостинг кода"},
+ {"title": "GitLab", "url": "https://gitlab.com", "description": "DevOps платформа"},
+ {"title": "Stack Overflow", "url": "https://stackoverflow.com", "description": "Q&A для программистов"},
+ {"title": "MDN Web Docs", "url": "https://developer.mozilla.org", "description": "Документация веб-технологий"},
+ {"title": "Visual Studio Code", "url": "https://code.visualstudio.com", "description": "Редактор кода"},
+ {"title": "Docker Hub", "url": "https://hub.docker.com", "description": "Контейнеры Docker"},
+ {"title": "npm", "url": "https://npmjs.com", "description": "Пакетный менеджер Node.js"},
+ {"title": "Python.org", "url": "https://python.org", "description": "Официальный сайт Python"},
+ {"title": "Django", "url": "https://djangoproject.com", "description": "Веб-фреймворк Python"},
+ {"title": "React", "url": "https://reactjs.org", "description": "JavaScript библиотека"},
+ {"title": "Vue.js", "url": "https://vuejs.org", "description": "Прогрессивный фреймворк"},
+ {"title": "Angular", "url": "https://angular.io", "description": "TypeScript фреймворк"},
+ {"title": "Next.js", "url": "https://nextjs.org", "description": "React фреймворк"},
+ {"title": "Tailwind CSS", "url": "https://tailwindcss.com", "description": "Utility-first CSS"},
+ {"title": "Bootstrap", "url": "https://getbootstrap.com", "description": "CSS фреймворк"}
+ ]
+ },
+ {
+ "name": "Онлайн сервисы",
+ "description": "Полезные веб-сервисы и инструменты",
+ "links": [
+ {"title": "Google Drive", "url": "https://drive.google.com", "description": "Облачное хранилище"},
+ {"title": "Dropbox", "url": "https://dropbox.com", "description": "Синхронизация файлов"},
+ {"title": "Notion", "url": "https://notion.so", "description": "Рабочее пространство"},
+ {"title": "Trello", "url": "https://trello.com", "description": "Управление проектами"},
+ {"title": "Slack", "url": "https://slack.com", "description": "Корпоративный мессенджер"},
+ {"title": "Zoom", "url": "https://zoom.us", "description": "Видеоконференции"},
+ {"title": "Figma", "url": "https://figma.com", "description": "Дизайн интерфейсов"},
+ {"title": "Canva", "url": "https://canva.com", "description": "Графический дизайн"},
+ {"title": "Unsplash", "url": "https://unsplash.com", "description": "Бесплатные фотографии"},
+ {"title": "Pixabay", "url": "https://pixabay.com", "description": "Стоковые изображения"},
+ {"title": "Google Translate", "url": "https://translate.google.com", "description": "Переводчик"},
+ {"title": "DeepL", "url": "https://deepl.com", "description": "Продвинутый переводчик"},
+ {"title": "Grammarly", "url": "https://grammarly.com", "description": "Проверка грамматики"},
+ {"title": "LastPass", "url": "https://lastpass.com", "description": "Менеджер паролей"},
+ {"title": "1Password", "url": "https://1password.com", "description": "Безопасность паролей"}
+ ]
+ },
+ {
+ "name": "Развлечения",
+ "description": "Игры, фильмы и развлекательный контент",
+ "links": [
+ {"title": "Netflix", "url": "https://netflix.com", "description": "Стриминг видео"},
+ {"title": "Spotify", "url": "https://spotify.com", "description": "Музыкальный стриминг"},
+ {"title": "Twitch", "url": "https://twitch.tv", "description": "Стриминг игр"},
+ {"title": "Steam", "url": "https://store.steampowered.com", "description": "Игровая платформа"},
+ {"title": "Epic Games", "url": "https://epicgames.com", "description": "Игровой магазин"},
+ {"title": "PlayStation Store", "url": "https://store.playstation.com", "description": "Игры для PS"},
+ {"title": "Xbox Store", "url": "https://xbox.com", "description": "Игры для Xbox"},
+ {"title": "IMDb", "url": "https://imdb.com", "description": "База данных фильмов"},
+ {"title": "Rotten Tomatoes", "url": "https://rottentomatoes.com", "description": "Рейтинги фильмов"},
+ {"title": "Kinopoisk", "url": "https://kinopoisk.ru", "description": "Российский кинопоиск"},
+ {"title": "Apple Music", "url": "https://music.apple.com", "description": "Музыка от Apple"},
+ {"title": "Amazon Prime", "url": "https://primevideo.com", "description": "Видео от Amazon"},
+ {"title": "Disney+", "url": "https://disneyplus.com", "description": "Контент Disney"},
+ {"title": "Hulu", "url": "https://hulu.com", "description": "Американский стриминг"},
+ {"title": "Paramount+", "url": "https://paramountplus.com", "description": "Стриминг Paramount"}
+ ]
+ },
+ {
+ "name": "Образование",
+ "description": "Платформы для обучения и саморазвития",
+ "links": [
+ {"title": "Coursera", "url": "https://coursera.org", "description": "Онлайн курсы университетов"},
+ {"title": "Udemy", "url": "https://udemy.com", "description": "Практические курсы"},
+ {"title": "edX", "url": "https://edx.org", "description": "Курсы от ведущих вузов"},
+ {"title": "Khan Academy", "url": "https://khanacademy.org", "description": "Бесплатное образование"},
+ {"title": "Codecademy", "url": "https://codecademy.com", "description": "Обучение программированию"},
+ {"title": "FreeCodeCamp", "url": "https://freecodecamp.org", "description": "Бесплатные курсы кода"},
+ {"title": "Pluralsight", "url": "https://pluralsight.com", "description": "Технические курсы"},
+ {"title": "LinkedIn Learning", "url": "https://linkedin.com/learning", "description": "Профессиональные навыки"},
+ {"title": "Skillshare", "url": "https://skillshare.com", "description": "Творческие курсы"},
+ {"title": "MasterClass", "url": "https://masterclass.com", "description": "Курсы от экспертов"},
+ {"title": "Duolingo", "url": "https://duolingo.com", "description": "Изучение языков"},
+ {"title": "Babbel", "url": "https://babbel.com", "description": "Языковые курсы"},
+ {"title": "TED", "url": "https://ted.com", "description": "Вдохновляющие лекции"},
+ {"title": "MIT OpenCourseWare", "url": "https://ocw.mit.edu", "description": "Курсы MIT"},
+ {"title": "Stanford Online", "url": "https://online.stanford.edu", "description": "Курсы Стэнфорда"}
+ ]
+ }
+]
+
+class APIClient:
+ def __init__(self, base_url, username, password):
+ self.base_url = base_url
+ self.session = requests.Session()
+ self.session.verify = False # Отключаем проверку SSL для локального тестирования
+ self.token = None
+ self.user_id = None
+ self.username = username
+ self.password = password
+ self.groups_endpoint = None # Сохраняем рабочий endpoint для групп
+
+ def login(self):
+ """Получение токена авторизации"""
+ print(f"🔐 Авторизация пользователя {self.username} на {self.base_url}...")
+
+ login_data = {
+ "username": self.username,
+ "password": self.password
+ }
+
+ try:
+ response = self.session.post(f"{self.base_url}/auth/login/", json=login_data, timeout=10)
+
+ if response.status_code == 200:
+ data = response.json()
+
+ # Пробуем разные поля для токена
+ self.token = (data.get('access') or
+ data.get('access_token') or
+ data.get('token'))
+
+ # Пробуем разные поля для user_id
+ user_data = data.get('user', {})
+ if isinstance(user_data, dict):
+ self.user_id = user_data.get('id')
+ else:
+ self.user_id = data.get('user_id')
+
+ print(f"Debug: Response data keys: {list(data.keys())}")
+ print(f"Debug: Token: {self.token}")
+
+ if self.token:
+ self.session.headers.update({
+ 'Authorization': f'Bearer {self.token}',
+ 'Content-Type': 'application/json'
+ })
+ print(f"✅ Успешная авторизация! User ID: {self.user_id}")
+ return True
+ else:
+ print("❌ Токен не найден в ответе")
+ return False
+
+ print(f"❌ Ошибка авторизации: {response.status_code}")
+ if response.status_code != 502: # Не показываем HTML для 502
+ print(f"Response: {response.text[:200]}")
+ return False
+
+ except requests.exceptions.RequestException as e:
+ print(f"❌ Ошибка подключения: {e}")
+ return False
+
+ def create_group(self, group_data):
+ """Создание группы ссылок"""
+ print(f"📁 Создание группы '{group_data['name']}'...")
+
+ payload = {
+ "name": group_data["name"],
+ "description": group_data.get("description", ""),
+ "is_favorite": random.choice([True, False]),
+ "header_color": f"#{random.randint(0, 0xFFFFFF):06x}"
+ }
+
+ # Пробуем разные endpoint'ы для групп
+ endpoints_to_try = [
+ f"{self.base_url}/link-groups/",
+ f"{self.base_url}/linkgroups/",
+ f"{self.base_url}/groups/",
+ f"{self.base_url}/link_groups/"
+ ]
+
+ for endpoint in endpoints_to_try:
+ try:
+ response = self.session.post(endpoint, json=payload, timeout=10)
+ if response.status_code == 201:
+ group = response.json()
+ print(f"✅ Группа '{group_data['name']}' создана с ID: {group['id']}")
+ self.groups_endpoint = endpoint # Запоминаем рабочий endpoint
+ return group
+ elif response.status_code == 404:
+ continue # Пробуем следующий endpoint
+ else:
+ print(f"❌ Ошибка создания группы через {endpoint}: {response.status_code}")
+ if response.status_code != 502:
+ print(f"Response: {response.text[:200]}")
+ continue
+ except Exception as e:
+ print(f"❌ Ошибка подключения к {endpoint}: {e}")
+ continue
+
+ print(f"❌ Не удалось создать группу '{group_data['name']}' через все endpoints")
+ return None
+
+ def create_link(self, group_id, link_data):
+ """Создание ссылки в группе"""
+ payload = {
+ "title": link_data["title"],
+ "url": link_data["url"],
+ "description": link_data.get("description", ""),
+ "group": group_id
+ }
+
+ # Пробуем разные endpoint'ы для ссылок
+ endpoints_to_try = [
+ f"{self.base_url}/links/",
+ f"{self.base_url}/link/",
+ f"{self.base_url}/api/links/"
+ ]
+
+ for endpoint in endpoints_to_try:
+ try:
+ response = self.session.post(endpoint, json=payload, timeout=10)
+ if response.status_code == 201:
+ link = response.json()
+ print(f" ✅ Ссылка '{link_data['title']}' добавлена")
+ return link
+ elif response.status_code == 404:
+ continue # Пробуем следующий endpoint
+ else:
+ print(f" ❌ Ошибка создания ссылки '{link_data['title']}' через {endpoint}: {response.status_code}")
+ continue
+ except Exception as e:
+ print(f" ❌ Ошибка подключения к {endpoint}: {e}")
+ continue
+
+ print(f" ❌ Не удалось создать ссылку '{link_data['title']}' через все endpoints")
+ return None
+
+ def create_groups_and_links(self):
+ """Создание всех групп и ссылок"""
+ if not self.login():
+ return False
+
+ print(f"\n🚀 Начинаем создание {len(GROUPS_DATA)} групп...")
+
+ created_groups = []
+ total_links = 0
+
+ for group_data in GROUPS_DATA:
+ # Создаем группу
+ group = self.create_group(group_data)
+ if not group:
+ continue
+
+ created_groups.append(group)
+
+ # Создаем ссылки в группе
+ print(f" 📎 Добавляем {len(group_data['links'])} ссылок в группу...")
+ for link_data in group_data['links']:
+ link = self.create_link(group['id'], link_data)
+ if link:
+ total_links += 1
+
+ print(f"\n🎉 Готово!")
+ print(f"📁 Создано групп: {len(created_groups)}")
+ print(f"📎 Создано ссылок: {total_links}")
+
+ return True
+
+def main():
+ print("🔗 Создание тестовых данных для профиля trevor")
+ print("=" * 50)
+
+ # Пробуем разные API endpoints
+ for api_base in API_ENDPOINTS:
+ print(f"\n🔍 Пробуем подключиться к: {api_base}")
+ client = APIClient(api_base, USERNAME, PASSWORD)
+
+ try:
+ success = client.create_groups_and_links()
+ if success:
+ print("\n✨ Все данные успешно созданы!")
+ return
+ else:
+ print(f"\n💥 Не удалось создать данные через {api_base}")
+ continue
+ except Exception as e:
+ print(f"\n💥 Ошибка с {api_base}: {e}")
+ continue
+
+ print("\n💥 Не удалось подключиться ни к одному API endpoint")
+ print("🔄 Попробуйте позже, когда сервер будет доступен")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/DEPLOYMENT_INSTRUCTIONS.md b/docs/DEPLOYMENT_INSTRUCTIONS.md
similarity index 100%
rename from DEPLOYMENT_INSTRUCTIONS.md
rename to docs/DEPLOYMENT_INSTRUCTIONS.md
diff --git a/TEMPLATES_AND_GROUPS_REPORT.md b/docs/TEMPLATES_AND_GROUPS_REPORT.md
similarity index 100%
rename from TEMPLATES_AND_GROUPS_REPORT.md
rename to docs/TEMPLATES_AND_GROUPS_REPORT.md
diff --git a/frontend/linktree-frontend/src/app/[username]/page.tsx b/frontend/linktree-frontend/src/app/[username]/page.tsx
index d70717d..b1ac072 100644
--- a/frontend/linktree-frontend/src/app/[username]/page.tsx
+++ b/frontend/linktree-frontend/src/app/[username]/page.tsx
@@ -617,11 +617,17 @@ export default function UserPage({
{group.description}
)}
({
+ id: link.id,
+ title: link.title,
+ url: link.url,
+ description: link.description,
+ image: designSettings.show_link_icons ? link.icon_url : undefined
+ }))}
layout="cards"
initialShowCount={8}
- overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined}
- overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined}
+ overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
+ overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
@@ -694,7 +700,13 @@ export default function UserPage({
)}
({
+ id: link.id,
+ title: link.title,
+ url: link.url,
+ description: link.description,
+ image: designSettings.show_link_icons ? link.icon_url : undefined
+ }))}
layout="timeline"
initialShowCount={5}
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
@@ -780,9 +792,15 @@ export default function UserPage({
({
+ id: link.id,
+ title: link.title,
+ url: link.url,
+ description: link.description,
+ image: designSettings.show_link_icons ? link.icon_url : undefined
+ }))}
layout="magazine"
- initialShowCount={index === 0 ? 5 : 3}
+ initialShowCount={index === 0 ? 8 : 6}
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/>
diff --git a/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
index 79681cc..460586e 100644
--- a/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
+++ b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
@@ -37,7 +37,11 @@ export function ExpandableGroup({
opacity: overlayOpacity
} : undefined
+ // Добавляем отладочную информацию
+ console.log(`ExpandableGroup: ${links.length} links, initialShowCount: ${initialShowCount}, layout: ${layout}`)
+
if (links.length <= initialShowCount) {
+ console.log(`No expand button needed: ${links.length} <= ${initialShowCount}`)
return (
{links.map((link) => (
@@ -57,6 +61,8 @@ export function ExpandableGroup({
const visibleLinks = isExpanded ? links : links.slice(0, initialShowCount)
const hiddenCount = links.length - initialShowCount
+ console.log(`Showing expand button: visibleLinks: ${visibleLinks.length}, hiddenCount: ${hiddenCount}`)
+
return (
{visibleLinks.map((link) => (