new models, frontend functions, public pages

This commit is contained in:
2025-05-07 15:41:03 +09:00
parent 91f0d54563
commit 18497d4343
784 changed files with 124024 additions and 289 deletions

View 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>
</>
);
}

View 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>
)
}