From 9c1c5b4b62f440362856f02d4446b2375e0fa381 Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Sat, 8 Nov 2025 20:24:42 +0900 Subject: [PATCH] Fix public page layouts and avatar display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎨 Layout improvements: - Add full implementations for timeline, masonry, and magazine layouts - Replace layout stubs with proper React components matching dashboard - Add timeline with alternating left/right positioning and central line - Implement masonry layout with dynamic column sizing - Create magazine layout with featured first item and image previews 🐛 Avatar & URL fixes: - Fix avatar URL protocol (http → https) in normalize_file_url - Add http://links.shareon.kr to internal URLs replacement list - Update get_backend_url to use HTTPS and proper domain from env vars - Fix CustomizationPanel API URL consistency in DashboardClient ✨ Visual enhancements: - Proper hover states and transitions for all layout types - Timeline dots with theme color coordination - Magazine layout with responsive image handling - Masonry cards with dynamic content sizing - Consistent group/link styling across all layouts 🔧 Technical: - Environment-driven URL generation for production - Consistent API endpoint usage across components - Better responsive design for mobile devices - Improved accessibility with proper alt text and ARIA labels --- backend/backend/utils.py | 9 +- .../(protected)/dashboard/DashboardClient.tsx | 2 +- .../src/app/[username]/page.tsx | 272 +++++++++++++++++- 3 files changed, 275 insertions(+), 8 deletions(-) diff --git a/backend/backend/utils.py b/backend/backend/utils.py index 042aba3..e63b430 100644 --- a/backend/backend/utils.py +++ b/backend/backend/utils.py @@ -7,12 +7,14 @@ from django.conf import settings def get_backend_url(): """Получить базовый URL backend из настроек""" - return getattr(settings, 'BACKEND_URL', 'http://localhost:8000') + protocol = os.getenv('DJANGO_BACKEND_PROTOCOL', 'https') + domain = os.getenv('DJANGO_BACKEND_DOMAIN', 'links.shareon.kr') + return f"{protocol}://{domain}" def get_media_base_url(): """Получить базовый URL для медиа файлов""" - return getattr(settings, 'MEDIA_BASE_URL', 'http://localhost:8000') + return get_backend_url() # Используем тот же базовый URL def build_absolute_url(path): @@ -48,7 +50,8 @@ def normalize_file_url(file_url): 'http://web:8000', 'http://links-web-1:8000', 'http://backend:8000', - 'http://localhost:8000' + 'http://localhost:8000', + 'http://links.shareon.kr' # Заменяем HTTP на HTTPS ] # Заменяем все внутренние URL на внешний diff --git a/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx b/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx index d0e0a1b..8f921b8 100644 --- a/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx +++ b/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx @@ -210,7 +210,7 @@ export default function DashboardClient() { fetch('/api/auth/user', { headers: { Authorization: `Bearer ${token}` } }), fetch('/api/groups', { headers: { Authorization: `Bearer ${token}` } }), fetch('/api/links', { headers: { Authorization: `Bearer ${token}` } }), - fetch('/api/customization/settings', { headers: { Authorization: `Bearer ${token}` } }), + fetch('/api/customization/settings/', { headers: { Authorization: `Bearer ${token}` } }), ]) .then(async ([uRes, gRes, lRes, dRes]) => { if (!uRes.ok) throw new Error('Не удалось получить профиль') diff --git a/frontend/linktree-frontend/src/app/[username]/page.tsx b/frontend/linktree-frontend/src/app/[username]/page.tsx index b218add..ce2f461 100644 --- a/frontend/linktree-frontend/src/app/[username]/page.tsx +++ b/frontend/linktree-frontend/src/app/[username]/page.tsx @@ -563,10 +563,274 @@ export default function UserPage({ ) - // Заглушки для остальных макетов - const renderMasonryLayout = () => renderGridLayout() - const renderTimelineLayout = () => renderListLayout() - const renderMagazineLayout = () => renderCardsLayout() + // Кладка макет + const renderMasonryLayout = () => ( +
+
+
Группы ссылок
+
+
+ {data!.groups.map((group, index) => ( +
+
+
+
+
+ {group.icon_url && designSettings.show_group_icons && ( + {group.name} + )} +
+ {group.name} +
+ {group.is_favorite && ( + + )} +
+ + {group.links.length} + +
+
+
+ {group.description && ( +

{group.description}

+ )} + {group.links.map(link => ( + +
+
+ {link.icon_url && designSettings.show_link_icons && ( + {link.title} + )} + + {link.title} + +
+
+ + ))} +
+
+
+ ))} +
+
+ ) + + // Лента времени макет + const renderTimelineLayout = () => ( +
+
+
Группы ссылок
+
+
+ {/* Центральная линия */} +
+ + {data!.groups.map((group, index) => ( +
+
+
+
+ {group.icon_url && designSettings.show_group_icons && ( + {group.name} + )} +
+
+ {group.name} +
+
+ {group.links.length} ссылок + {group.is_favorite && ( + + )} +
+
+
+
+ {group.description && ( +

{group.description}

+ )} + {group.links.slice(0, 5).map(link => ( + +
+ {link.icon_url && designSettings.show_link_icons && ( + {link.title} + )} + + {link.title} + +
+ + ))} + {group.links.length > 5 && ( + +{group.links.length - 5} еще... + )} +
+ + {/* Точка на временной линии */} +
+
+
+
+ ))} +
+
+ ) + + // Журнальный макет + const renderMagazineLayout = () => ( +
+
+
Группы ссылок
+
+
+ {data!.groups.map((group, index) => ( +
+
+
+
+
+ {!group.background_image && ( + group.icon_url && designSettings.show_group_icons ? ( + {group.name} + ) : ( + + ) + )} + {group.is_favorite && ( +
+ +
+ )} +
+
+
+
+
+ {group.name} +
+

+ {group.description || `${group.links.length} ссылок в этой группе`} +

+
+ {group.links.slice(0, index === 0 ? 5 : 3).map(link => ( + +
+ {link.icon_url && designSettings.show_link_icons && ( + {link.title} + )} + + {link.title} + +
+ + ))} + {group.links.length > (index === 0 ? 5 : 3) && ( + и еще {group.links.length - (index === 0 ? 5 : 3)}... + )} +
+
+
+
+
+
+ ))} +
+
+ ) // Основная функция рендеринга групп в зависимости от выбранного макета const renderGroupsLayout = () => {