+ Приведены все функции приложения в рабочий вид
+ Наведен порядок в файлах проекта + Наведен порядок в документации + Настроены скрипты установки, развертки и так далее, расширен MakeFile
This commit is contained in:
@@ -1,139 +1,746 @@
|
||||
// src/app/[username]/page.tsx
|
||||
'use client'
|
||||
|
||||
import { notFound } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface LinkItem {
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
image?: string
|
||||
icon?: string
|
||||
icon_url?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface Group {
|
||||
id: number
|
||||
name: string
|
||||
image?: string
|
||||
description?: string
|
||||
icon?: string
|
||||
icon_url?: string
|
||||
header_color?: string
|
||||
background_image?: string
|
||||
is_favorite: boolean
|
||||
links: LinkItem[]
|
||||
}
|
||||
|
||||
interface UserGroupsData {
|
||||
username: string
|
||||
full_name?: string
|
||||
bio?: string
|
||||
avatar?: string
|
||||
cover?: string
|
||||
design_settings: PublicDesignSettings
|
||||
groups: Group[]
|
||||
}
|
||||
|
||||
export default async function UserPage({
|
||||
interface PublicDesignSettings {
|
||||
theme_color: string
|
||||
background_image?: string
|
||||
dashboard_layout: string
|
||||
groups_default_expanded: boolean
|
||||
show_group_icons: boolean
|
||||
show_link_icons: boolean
|
||||
dashboard_background_color: string
|
||||
font_family: string
|
||||
header_text_color?: string
|
||||
group_text_color?: string
|
||||
link_text_color?: string
|
||||
cover_overlay_enabled?: boolean
|
||||
cover_overlay_color?: string
|
||||
cover_overlay_opacity?: number
|
||||
}
|
||||
|
||||
export default function UserPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ username: string }>
|
||||
}) {
|
||||
const { username } = await params
|
||||
const API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
|
||||
|
||||
const res = await fetch(`${API}/api/users/${username}/public`, {
|
||||
cache: 'no-store',
|
||||
const [username, setUsername] = useState<string>('')
|
||||
const [data, setData] = useState<UserGroupsData | null>(null)
|
||||
const [designSettings, setDesignSettings] = useState<PublicDesignSettings>({
|
||||
theme_color: '#ffffff',
|
||||
dashboard_layout: 'list',
|
||||
groups_default_expanded: true,
|
||||
show_group_icons: true,
|
||||
show_link_icons: true,
|
||||
dashboard_background_color: '#f8f9fa',
|
||||
font_family: 'sans-serif',
|
||||
header_text_color: '#000000',
|
||||
group_text_color: '#333333',
|
||||
link_text_color: '#666666'
|
||||
})
|
||||
if (res.status === 404) return notFound()
|
||||
if (!res.ok) throw new Error('Ошибка загрузки публичных данных')
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<number>>(new Set())
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const data: UserGroupsData = await res.json()
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const resolvedParams = await params
|
||||
const usernameValue = resolvedParams.username
|
||||
setUsername(usernameValue)
|
||||
|
||||
return (
|
||||
<main className="pb-8">
|
||||
<div className="container">
|
||||
<h2 className="text-center mb-4">{data.username}</h2>
|
||||
// Определяем API URL в зависимости от окружения
|
||||
const API = typeof window !== 'undefined'
|
||||
? (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000') // клиент
|
||||
: 'http://web:8000' // сервер в Docker
|
||||
|
||||
<div className="accordion" id="groupsAccordion">
|
||||
{data.groups.map((group) => {
|
||||
const groupId = `group-${group.id}`
|
||||
console.log('Loading data for user:', usernameValue)
|
||||
console.log('API URL:', API)
|
||||
console.log('Is client side:', typeof window !== 'undefined')
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.id}
|
||||
className="accordion-item mb-3"
|
||||
style={{ border: '1px solid #dee2e6', borderRadius: 4 }}
|
||||
try {
|
||||
// Загружаем только данные пользователя (они уже включают настройки дизайна)
|
||||
const userRes = await fetch(`${API}/api/users/${usernameValue}/public/`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
||||
console.log('User response status:', userRes.status)
|
||||
|
||||
if (userRes.status === 404) {
|
||||
notFound()
|
||||
return
|
||||
}
|
||||
if (!userRes.ok) {
|
||||
throw new Error('Ошибка загрузки публичных данных')
|
||||
}
|
||||
|
||||
const userData: UserGroupsData = await userRes.json()
|
||||
setData(userData)
|
||||
|
||||
// Используем настройки дизайна из ответа пользователя
|
||||
if (userData.design_settings) {
|
||||
setDesignSettings(userData.design_settings)
|
||||
|
||||
// Если группы должны быть развернуты по умолчанию
|
||||
if (userData.design_settings.groups_default_expanded) {
|
||||
setExpandedGroups(new Set(userData.groups.map(g => g.id)))
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadData()
|
||||
}, [params])
|
||||
|
||||
// Функция для переключения видимости группы (для сайдбар-макета)
|
||||
const toggleGroup = (groupId: number) => {
|
||||
setExpandedGroups(prev => {
|
||||
const newSet = new Set(prev)
|
||||
if (newSet.has(groupId)) {
|
||||
newSet.delete(groupId)
|
||||
} else {
|
||||
newSet.add(groupId)
|
||||
}
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
||||
// Базовый список (по умолчанию)
|
||||
const renderListLayout = () => (
|
||||
<div className="row">
|
||||
{data!.groups.map((group) => {
|
||||
const isExpanded = expandedGroups.has(group.id)
|
||||
return (
|
||||
<div key={group.id} className="col-12 mb-4">
|
||||
<div className="card shadow-sm">
|
||||
<div
|
||||
className="card-header d-flex align-items-center justify-content-between"
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
style={{
|
||||
backgroundColor: group.header_color || designSettings.theme_color + '20',
|
||||
borderColor: group.header_color || designSettings.theme_color,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<h2 className="accordion-header" id={`${groupId}-header`}>
|
||||
<button
|
||||
className="accordion-button collapsed d-flex align-items-center"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={`#${groupId}-collapse`}
|
||||
aria-expanded="false"
|
||||
aria-controls={`${groupId}-collapse`}
|
||||
>
|
||||
{group.image && (
|
||||
<Image
|
||||
src={
|
||||
group.image.startsWith('http')
|
||||
? group.image
|
||||
: `${API}${group.image}`
|
||||
}
|
||||
alt={group.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="me-2 rounded"
|
||||
priority
|
||||
/>
|
||||
<div className="d-flex align-items-center flex-grow-1">
|
||||
{designSettings.show_group_icons && group.icon && (
|
||||
<img
|
||||
src={group.icon}
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
className="me-2 rounded"
|
||||
/>
|
||||
)}
|
||||
<h5 className="mb-0 flex-grow-1" style={{ color: designSettings.group_text_color || designSettings.theme_color }}>
|
||||
{group.name}
|
||||
</h5>
|
||||
<div className="d-flex align-items-center ms-2">
|
||||
{group.is_favorite && (
|
||||
<i className="bi bi-star-fill text-warning me-2" title="Избранная группа"></i>
|
||||
)}
|
||||
<span className="me-2">{group.name}</span>
|
||||
<span className="badge bg-secondary rounded-pill">
|
||||
<span
|
||||
className="badge rounded-pill me-2"
|
||||
style={{
|
||||
backgroundColor: designSettings.theme_color,
|
||||
color: 'white'
|
||||
}}
|
||||
>
|
||||
{group.links.length}
|
||||
</span>
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<i className={`bi ${isExpanded ? 'bi-chevron-up' : 'bi-chevron-down'} ms-2`}></i>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div
|
||||
id={`${groupId}-collapse`}
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby={`${groupId}-header`}
|
||||
data-bs-parent="#groupsAccordion"
|
||||
className="card-body"
|
||||
style={{
|
||||
backgroundImage: group.background_image ? `url(${group.background_image})` : 'none',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}}
|
||||
>
|
||||
<div className="accordion-body">
|
||||
{group.links.length > 0 ? (
|
||||
<ul className="list-unstyled">
|
||||
{group.links.map((link) => (
|
||||
<li
|
||||
key={link.id}
|
||||
className="mb-2 p-2 bg-white rounded shadow-sm"
|
||||
style={{ marginBottom: 5 }}
|
||||
{group.description && (
|
||||
<p className="text-muted mb-3">{group.description}</p>
|
||||
)}
|
||||
{group.links.length > 0 ? (
|
||||
<div className="row">
|
||||
{group.links.map((link) => (
|
||||
<div key={link.id} className="col-12 col-md-6 col-lg-4 mb-3">
|
||||
<Link
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="d-block text-decoration-none"
|
||||
>
|
||||
<div className="d-flex align-items-center">
|
||||
{link.image && (
|
||||
<Image
|
||||
src={
|
||||
link.image.startsWith('http')
|
||||
? link.image
|
||||
: `${API}${link.image}`
|
||||
}
|
||||
alt={link.title}
|
||||
width={24}
|
||||
height={24}
|
||||
className="me-2"
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
<Link
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-grow-1"
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
<div className="card h-100 shadow-sm border-start border-3 hover-shadow"
|
||||
style={{
|
||||
borderColor: designSettings.theme_color + '60',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div className="card-body d-flex align-items-center">
|
||||
{designSettings.show_link_icons && link.icon_url && (
|
||||
<Image
|
||||
src={link.icon_url}
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className="me-2 rounded"
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
<div className="flex-grow-1">
|
||||
<h6 className="card-title mb-1" style={{ color: designSettings.link_text_color || designSettings.theme_color }}>
|
||||
{link.title}
|
||||
</h6>
|
||||
{link.description && (
|
||||
<p className="card-text small text-muted mb-0">{link.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-muted mb-0">
|
||||
В этой группе пока нет ссылок.
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted mb-0">
|
||||
В этой группе пока нет ссылок.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Сетка групп
|
||||
const renderGridLayout = () => (
|
||||
<div className="row">
|
||||
{data!.groups.map((group) => (
|
||||
<div key={group.id} className="col-12 col-md-6 col-lg-4 mb-4">
|
||||
<div className="card h-100 shadow-sm">
|
||||
<div
|
||||
className="card-header text-center"
|
||||
style={{
|
||||
backgroundColor: group.header_color || designSettings.theme_color + '20',
|
||||
borderColor: group.header_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
{designSettings.show_group_icons && group.icon && (
|
||||
<img
|
||||
src={group.icon}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
className="rounded-circle mb-2"
|
||||
/>
|
||||
)}
|
||||
<h6 className="mb-0" style={{ color: designSettings.group_text_color || designSettings.theme_color }}>
|
||||
{group.name}
|
||||
</h6>
|
||||
{group.is_favorite && (
|
||||
<i className="bi bi-star-fill text-warning mt-1"></i>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="card-body"
|
||||
style={{
|
||||
backgroundImage: group.background_image ? `url(${group.background_image})` : 'none',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}}
|
||||
>
|
||||
{group.description && (
|
||||
<p className="text-muted small mb-3">{group.description}</p>
|
||||
)}
|
||||
<div className="d-grid gap-2">
|
||||
{group.links.map((link) => (
|
||||
<Link
|
||||
key={link.id}
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-outline-primary btn-sm d-flex align-items-center"
|
||||
style={{
|
||||
borderColor: designSettings.theme_color,
|
||||
color: designSettings.link_text_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
{designSettings.show_link_icons && link.icon && (
|
||||
<img
|
||||
src={link.icon}
|
||||
alt=""
|
||||
width={16}
|
||||
height={16}
|
||||
className="me-2 rounded"
|
||||
/>
|
||||
)}
|
||||
{link.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Карточки (большие карточки с описанием)
|
||||
const renderCardsLayout = () => (
|
||||
<div className="row">
|
||||
{data!.groups.map((group) => (
|
||||
<div key={group.id} className="col-12 col-lg-6 mb-4">
|
||||
<div className="card h-100 shadow">
|
||||
<div
|
||||
className="card-header d-flex align-items-center"
|
||||
style={{
|
||||
backgroundColor: group.header_color || designSettings.theme_color + '20',
|
||||
borderColor: group.header_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
{designSettings.show_group_icons && group.icon && (
|
||||
<img
|
||||
src={group.icon}
|
||||
alt=""
|
||||
width={48}
|
||||
height={48}
|
||||
className="me-3 rounded"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<h5 className="mb-1" style={{ color: designSettings.group_text_color || designSettings.theme_color }}>
|
||||
{group.name}
|
||||
</h5>
|
||||
{group.description && (
|
||||
<p className="text-muted mb-0 small">{group.description}</p>
|
||||
)}
|
||||
</div>
|
||||
{group.is_favorite && (
|
||||
<i className="bi bi-star-fill text-warning ms-auto"></i>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="card-body"
|
||||
style={{
|
||||
backgroundImage: group.background_image ? `url(${group.background_image})` : 'none',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
{group.links.map((link) => (
|
||||
<div key={link.id} className="col-12 col-md-6 mb-2">
|
||||
<Link
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="d-flex align-items-center p-2 border rounded text-decoration-none hover-shadow"
|
||||
style={{
|
||||
borderColor: designSettings.theme_color + '40',
|
||||
color: designSettings.link_text_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
{designSettings.show_link_icons && link.icon && (
|
||||
<img
|
||||
src={link.icon}
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className="me-2 rounded"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<h6 className="mb-0" style={{ color: designSettings.link_text_color || designSettings.theme_color }}>
|
||||
{link.title}
|
||||
</h6>
|
||||
{link.description && (
|
||||
<small className="text-muted">{link.description}</small>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Компактный макет
|
||||
const renderCompactLayout = () => (
|
||||
<div className="row">
|
||||
{data!.groups.map((group) => (
|
||||
<div key={group.id} className="col-12 mb-3">
|
||||
<div className="border-start border-4 ps-3" style={{ borderColor: group.header_color || designSettings.theme_color }}>
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
{designSettings.show_group_icons && group.icon_url && (
|
||||
<Image
|
||||
src={group.icon_url}
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
className="me-2 rounded"
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
<h6 className="mb-0" style={{ color: designSettings.group_text_color || designSettings.theme_color }}>
|
||||
{group.name}
|
||||
</h6>
|
||||
{group.is_favorite && (
|
||||
<i className="bi bi-star-fill text-warning ms-2"></i>
|
||||
)}
|
||||
</div>
|
||||
<div className="row">
|
||||
{group.links.map((link) => (
|
||||
<div key={link.id} className="col-auto mb-1">
|
||||
<Link
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-outline-secondary btn-sm d-flex align-items-center"
|
||||
style={{
|
||||
borderColor: designSettings.theme_color + '60',
|
||||
color: designSettings.link_text_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
{designSettings.show_link_icons && link.icon && (
|
||||
<img
|
||||
src={link.icon}
|
||||
alt=""
|
||||
width={16}
|
||||
height={16}
|
||||
className="me-1 rounded"
|
||||
/>
|
||||
)}
|
||||
<small className="text-truncate" style={{ color: designSettings.link_text_color || designSettings.theme_color }}>
|
||||
{link.title}
|
||||
</small>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Боковая панель
|
||||
const renderSidebarLayout = () => (
|
||||
<div className="row">
|
||||
<div className="col-12 col-lg-3 mb-4">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h6 className="mb-0">Группы</h6>
|
||||
</div>
|
||||
<div className="list-group list-group-flush">
|
||||
{data!.groups.map((group) => (
|
||||
<button
|
||||
key={group.id}
|
||||
className={`list-group-item list-group-item-action d-flex align-items-center ${expandedGroups.has(group.id) ? 'active' : ''}`}
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
style={{
|
||||
borderColor: expandedGroups.has(group.id) ? designSettings.theme_color : undefined
|
||||
}}
|
||||
>
|
||||
{designSettings.show_group_icons && group.icon && (
|
||||
<img
|
||||
src={group.icon}
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className="me-2 rounded"
|
||||
/>
|
||||
)}
|
||||
<span className="flex-grow-1">{group.name}</span>
|
||||
{group.is_favorite && (
|
||||
<i className="bi bi-star-fill text-warning ms-1"></i>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-lg-9">
|
||||
{data!.groups.filter(g => expandedGroups.has(g.id)).map((group) => (
|
||||
<div key={group.id} className="card mb-4">
|
||||
<div
|
||||
className="card-header"
|
||||
style={{
|
||||
backgroundColor: group.header_color || designSettings.theme_color + '20',
|
||||
borderColor: group.header_color || designSettings.theme_color
|
||||
}}
|
||||
>
|
||||
<h6 className="mb-0" style={{ color: designSettings.group_text_color || designSettings.theme_color }}>
|
||||
{group.name}
|
||||
</h6>
|
||||
{group.description && (
|
||||
<small className="text-muted">{group.description}</small>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="card-body"
|
||||
style={{
|
||||
backgroundImage: group.background_image ? `url(${group.background_image})` : 'none',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
{group.links.map((link) => (
|
||||
<div key={link.id} className="col-12 col-md-6 col-lg-4 mb-3">
|
||||
<Link
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="card h-100 text-decoration-none border"
|
||||
style={{ borderColor: designSettings.theme_color + '40' }}
|
||||
>
|
||||
<div className="card-body d-flex align-items-center">
|
||||
{designSettings.show_link_icons && link.icon && (
|
||||
<img
|
||||
src={link.icon}
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className="me-2 rounded"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<h6 className="mb-1" style={{ color: designSettings.link_text_color || designSettings.theme_color }}>
|
||||
{link.title}
|
||||
</h6>
|
||||
{link.description && (
|
||||
<small className="text-muted">{link.description}</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Заглушки для остальных макетов
|
||||
const renderMasonryLayout = () => renderGridLayout()
|
||||
const renderTimelineLayout = () => renderListLayout()
|
||||
const renderMagazineLayout = () => renderCardsLayout()
|
||||
|
||||
// Основная функция рендеринга групп в зависимости от выбранного макета
|
||||
const renderGroupsLayout = () => {
|
||||
switch (designSettings.dashboard_layout) {
|
||||
case 'list':
|
||||
return renderListLayout()
|
||||
case 'grid':
|
||||
return renderGridLayout()
|
||||
case 'cards':
|
||||
return renderCardsLayout()
|
||||
case 'compact':
|
||||
return renderCompactLayout()
|
||||
case 'sidebar':
|
||||
return renderSidebarLayout()
|
||||
case 'masonry':
|
||||
return renderMasonryLayout()
|
||||
case 'timeline':
|
||||
return renderTimelineLayout()
|
||||
case 'magazine':
|
||||
return renderMagazineLayout()
|
||||
default:
|
||||
return renderListLayout()
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<main className="pb-8">
|
||||
<div className="container">
|
||||
<div className="text-center py-5">
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
// Стили для контейнера
|
||||
const containerStyle = {
|
||||
backgroundColor: designSettings.dashboard_background_color,
|
||||
fontFamily: designSettings.font_family,
|
||||
backgroundImage: designSettings.background_image ? `url(${designSettings.background_image})` : 'none',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundAttachment: 'fixed',
|
||||
minHeight: '100vh',
|
||||
paddingTop: '2rem', // отступ сверху для рамки фона
|
||||
paddingBottom: '2rem' // отступ снизу для рамки фона
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={containerStyle}>
|
||||
<div className="container">
|
||||
{/* Профиль пользователя */}
|
||||
<div className="row justify-content-center mb-5">
|
||||
<div className="col-12 col-md-8 col-lg-6">
|
||||
<div className="card shadow-lg border-0" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
borderRadius: '20px'
|
||||
}}>
|
||||
<div className="card-body text-center p-4">
|
||||
{/* Аватар пользователя */}
|
||||
{data.avatar && (
|
||||
<div className="mb-3 position-relative d-inline-block">
|
||||
<Image
|
||||
src={data.avatar}
|
||||
alt={data.username}
|
||||
width={120}
|
||||
height={120}
|
||||
className="rounded-circle border border-4 shadow-sm"
|
||||
style={{
|
||||
borderColor: designSettings.theme_color,
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
priority
|
||||
/>
|
||||
<div
|
||||
className="position-absolute bottom-0 end-0 rounded-circle border border-2 border-white"
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
backgroundColor: designSettings.theme_color
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Имя пользователя */}
|
||||
<h1 className="mb-2 fw-bold" style={{ color: designSettings.header_text_color || designSettings.theme_color }}>
|
||||
{data.full_name || data.username}
|
||||
</h1>
|
||||
|
||||
{/* Username если есть полное имя */}
|
||||
{data.full_name && (
|
||||
<p className="text-muted mb-2 fs-5">@{data.username}</p>
|
||||
)}
|
||||
|
||||
{/* Биография пользователя */}
|
||||
{data.bio && (
|
||||
<p className="text-muted mb-3 fs-6 px-3">{data.bio}</p>
|
||||
)}
|
||||
|
||||
{/* Статистика */}
|
||||
<div className="row text-center mt-3">
|
||||
<div className="col-6">
|
||||
<div className="fw-bold fs-4" style={{ color: designSettings.theme_color }}>
|
||||
{data.groups.length}
|
||||
</div>
|
||||
<small className="text-muted">Групп</small>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<div className="fw-bold fs-4" style={{ color: designSettings.theme_color }}>
|
||||
{data.groups.reduce((total, group) => total + group.links.length, 0)}
|
||||
</div>
|
||||
<small className="text-muted">Ссылок</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Обложка пользователя если есть */}
|
||||
{data.cover && (
|
||||
<div className="row justify-content-center mb-4">
|
||||
<div className="col-12 col-lg-10">
|
||||
<div className="card border-0 shadow position-relative">
|
||||
<Image
|
||||
src={data.cover}
|
||||
alt="Обложка"
|
||||
width={800}
|
||||
height={300}
|
||||
className="card-img rounded"
|
||||
style={{ objectFit: 'cover', maxHeight: '300px' }}
|
||||
/>
|
||||
{/* Cover overlay если включен */}
|
||||
{designSettings.cover_overlay_enabled && (
|
||||
<div
|
||||
className="position-absolute top-0 start-0 w-100 h-100 rounded"
|
||||
style={{
|
||||
backgroundColor: designSettings.cover_overlay_color || '#000000',
|
||||
opacity: designSettings.cover_overlay_opacity || 0.3,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Группы ссылок */}
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12">
|
||||
{renderGroupsLayout()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user