new models, frontend functions, public pages
This commit is contained in:
31
frontend/linktree-frontend/src/app/[username]/layout.tsx
Normal file
31
frontend/linktree-frontend/src/app/[username]/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// src/app/[username]/layout.tsx
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const dynamic = "force-dynamic"; // всегда свежие данные
|
||||
|
||||
export default function UserLayout({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Подключаем стили точно так же, как в root */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/bootstrap/css/bootstrap.min.css?h=608a9825a1f76f674715160908e57785"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/css/Lato.css?h=8253736d3a23b522f64b7e7d96d1d8ff"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
|
||||
/>
|
||||
|
||||
{/* Только контент пользователской страницы */}
|
||||
<main>{children}</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
141
frontend/linktree-frontend/src/app/[username]/page.tsx
Normal file
141
frontend/linktree-frontend/src/app/[username]/page.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
// src/app/[username]/page.tsx
|
||||
import { notFound } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface LinkItem {
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
interface Group {
|
||||
id: number
|
||||
name: string
|
||||
image?: string
|
||||
links: LinkItem[]
|
||||
}
|
||||
|
||||
interface UserGroupsData {
|
||||
username: string
|
||||
groups: Group[]
|
||||
}
|
||||
|
||||
export default async function UserPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ username: string }>
|
||||
}) {
|
||||
const { username } = await params
|
||||
const API = process.env.NEXT_PUBLIC_API_URL
|
||||
|
||||
const res = await fetch(`${API}/api/users/${username}/public`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
if (res.status === 404) return notFound()
|
||||
if (!res.ok) throw new Error('Ошибка загрузки публичных данных')
|
||||
|
||||
const data: UserGroupsData = await res.json()
|
||||
|
||||
return (
|
||||
<main className="pb-8">
|
||||
<div className="container">
|
||||
<h2 className="text-center mb-4">{data.username}</h2>
|
||||
|
||||
<div className="accordion" id="groupsAccordion">
|
||||
{data.groups.map((group) => {
|
||||
const groupId = `group-${group.id}`
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.id}
|
||||
className="accordion-item mb-3"
|
||||
style={{ border: '1px solid #dee2e6', borderRadius: 4 }}
|
||||
>
|
||||
<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
|
||||
/>
|
||||
)}
|
||||
<span className="me-2">{group.name}</span>
|
||||
<span className="badge bg-secondary rounded-pill">
|
||||
{group.links.length}
|
||||
</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id={`${groupId}-collapse`}
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby={`${groupId}-header`}
|
||||
data-bs-parent="#groupsAccordion"
|
||||
>
|
||||
<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 }}
|
||||
>
|
||||
<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>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-muted mb-0">
|
||||
В этой группе пока нет ссылок.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user