Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
449 changes: 295 additions & 154 deletions src/app/(app)/[lang]/kollegium/kollegium-bemutato/[slug]/page.tsx

Large diffs are not rendered by default.

61 changes: 55 additions & 6 deletions src/app/(app)/[lang]/kollegium/kollegium-bemutato/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,67 @@
export const dynamic = "force-dynamic";

import { PageHeader } from "@/components/common/PageHeader";
import { getDictionary } from "@/get-dictionary";
import { Locale } from "@/i18n-config";
import DormitoryCardsContainer from "../felveteli-tajekoztato/components/DormitoryCardsContainer";
import { getDormitories } from "@/lib/payload-cms";
import { Media } from "@/payload-types";
import { ImageCard } from "@/components/common/ImageCard";

export default async function AdmissionInformationPage({
export default async function DormitoriesOverviewPage({
params }: Readonly<{ params: Promise<{ lang: Locale }> }>){
const { lang } = await params;
const dictionary = await getDictionary(lang, 'dormitories');
const [dictionary, dormitories] = await Promise.all([
getDictionary(lang, "dormitories"),
getDormitories(),
]);

const d = dictionary.dormitories.admission_information;

const cards = dormitories
.map((dormitory) => {
const coverImage =
typeof dormitory.coverImage === "object" && dormitory.coverImage !== null
? (dormitory.coverImage as Media)
: null;

if (!coverImage?.url) {
return null;
}

return {
name: dormitory.name,
slug: dormitory.slug,
imageSrc: coverImage.url,
href: `/${lang}/kollegium/kollegium-bemutato/${dormitory.slug}`,
};
})
.filter((card): card is { name: string; slug: string; imageSrc: string; href: string } => card !== null);

return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-[#f9f4f0]">
<div className="container mx-auto px-2 md:px-4 py-8">
<PageHeader title={dictionary.dormitories.admission_information.introduction} />
<DormitoryCardsContainer dormitory={dictionary.dormitories.admission_information.dormitory} linkOrRoute="route"/>
<PageHeader title={d.introduction} />
<div className="rounded-b-2xl border-x border-b border-[#e9e2d6] bg-[#fffefc] p-6 md:p-8">
{cards.length > 0 ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
{cards.map((dormitory) => (
<ImageCard
key={dormitory.slug}
content={{
href: dormitory.href,
imageSrc: dormitory.imageSrc,
title: dormitory.name,
detailsLabel: d.details,
}}
/>
))}
</div>
) : (
<p className="font-open-sans text-sm leading-[1.6] text-[#6e6660]">
{d.no_results}
</p>
)}
</div>
</div>
</div>
)
Expand Down
185 changes: 185 additions & 0 deletions src/collections/Dormitories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import type { CollectionConfig } from "payload";
import { FixedToolbarFeature, lexicalEditor } from "@payloadcms/richtext-lexical";

const validateOptionalUrl = (val: string | null | undefined) => {
if (!val) return true;

try {
const parsed = new URL(val);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
return "A linknek érvényes HTTP vagy HTTPS URL-nek kell lennie (pl. https://example.com).";
}
return true;
} catch {
return "Érvénytelen URL formátum.";
}
};

export const Dormitories: CollectionConfig = {
slug: "dormitories",
labels: {
singular: "Kollégium",
plural: "Kollégiumok",
},
access: {
read: () => true,
},
admin: {
useAsTitle: "name",
defaultColumns: ["name", "slug", "capacity", "order"],
description: "A kollégium bemutató oldalon megjelenő kollégiumok kezelése.",
},
fields: [
{
name: "name",
label: "Név",
type: "text",
required: true,
},
{
name: "slug",
label: "Slug",
type: "text",
required: true,
unique: true,
admin: {
description: "Útvonalhoz használt azonosító, pl. baross, sch.",
},
},
{
name: "coverImage",
label: "Borítókép",
type: "upload",
relationTo: "media",
required: true,
},
{
name: "externalLink",
label: "Külső link",
type: "text",
required: false,
admin: {
description: "Opcionális külső oldal, ahová a Részletek gomb navigál.",
},
validate: validateOptionalUrl,
},
{
name: "description_hu",
label: "Leírás (magyar)",
type: "richText",
required: false,
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
],
}),
},
{
name: "description_en",
label: "Leírás (angol)",
type: "richText",
required: false,
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
],
}),
},
{
name: "capacity",
label: "Férőhelyek száma",
type: "number",
required: false,
},
{
name: "address_hu",
label: "Cím (magyar)",
type: "text",
required: false,
},
{
name: "address_en",
label: "Cím (angol)",
type: "text",
required: false,
},
{
name: "mapUrl",
label: "Google Maps link",
type: "text",
required: false,
admin: {
description: "Opcionális Google Maps vagy térkép link a címhez.",
},
validate: validateOptionalUrl,
},
{
name: "roomInfo_hu",
label: "Szobainformáció (magyar)",
type: "text",
required: false,
},
{
name: "roomInfo_en",
label: "Szobainformáció (angol)",
type: "text",
required: false,
},
{
name: "targetAudience_hu",
label: "Célközönség (magyar)",
type: "text",
required: false,
},
{
name: "targetAudience_en",
label: "Célközönség (angol)",
type: "text",
required: false,
},
{
name: "gallery",
label: "Kategorizált galéria",
type: "array",
required: false,
fields: [
{
name: "categoryName_hu",
label: "Kategória neve (magyar)",
type: "text",
required: true,
},
{
name: "categoryName_en",
label: "Kategória neve (angol)",
type: "text",
required: false,
},
{
name: "images",
label: "Képek",
type: "array",
required: false,
fields: [
{
name: "image",
label: "Kép",
type: "upload",
relationTo: "media",
required: true,
},
],
},
],
},
{
name: "order",
label: "Sorrend",
type: "number",
required: true,
defaultValue: 0,
},
],
};
120 changes: 82 additions & 38 deletions src/components/common/ImageCard.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,88 @@
"use client";
import { Card, CardContent } from "@/components/ui/card";
import { ArrowRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";

interface ImageCardProps {
href: string;
imageSrc: string;
title: string;
description?: string;
href: string;
imageSrc: string;
title: string;
description?: string;
detailsLabel?: string;
}

export function ImageCard({ content }: Readonly<{content: ImageCardProps }> ) {
const { href, imageSrc, title, description} = content;
const shouldBeBlank = href.startsWith('http');
export function ImageCard({ content }: Readonly<{ content: ImageCardProps }>) {
const {
href,
imageSrc,
title,
description,
detailsLabel = "Részletek",
} = content;
const isExternal = href.startsWith("http");

const card = (
<article className="group relative h-full pb-4 transition-transform duration-200 hover:-translate-y-1">
<div
className="absolute inset-x-0 bottom-0 top-16 rounded-2xl border border-[#e9e2d6] bg-[#fffefc] transition-colors duration-200 group-hover:border-[#d3afaf]"
aria-hidden="true"
/>

<div className="relative z-10 mx-4 aspect-[349/296] overflow-hidden rounded-2xl border border-[#e9e2d6] bg-[#f9f4f0] sm:mx-8">
<Image
src={imageSrc}
alt={title}
fill
sizes="(max-width: 640px) calc(100vw - 64px), (max-width: 1024px) 45vw, 349px"
className="object-cover transition-transform duration-300 group-hover:scale-105"
/>
</div>

<div className="relative z-10 flex flex-col gap-4 px-4 pt-8 sm:px-8">
<div className="flex min-h-[29px] items-end justify-center text-center">
<h3 className="font-playfair text-[22px] font-bold uppercase leading-[1.3] text-[#1a1a1a] transition-colors duration-200 group-hover:text-[#862633]">
{title}
</h3>
</div>

{description ? (
<p className="font-open-sans text-center text-sm leading-[1.6] text-[#6e6660]">
{description}
</p>
) : null}

<div className="flex flex-col gap-4 border-t border-[#e9e2d6] pt-4">
<div className="flex h-[22px] items-center gap-4">
<span className="font-open-sans text-sm font-semibold leading-[1.6] text-[#862633]">
{detailsLabel}
</span>
<span className="flex flex-1 justify-end text-[#862633]">
<ArrowRight
className="h-6 w-6 transition-transform duration-200 group-hover:translate-x-1"
aria-hidden="true"
/>
</span>
</div>
</div>
</div>
</article>
);

if (isExternal) {
return (
<a href={href} target={shouldBeBlank ? "_blank" : "_self"} rel={shouldBeBlank ? "noopener noreferrer" : ""} className="block w-full sm:w-auto self-stretch">
<Card className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer w-full sm:w-64 h-full min-h-[17rem] flex flex-col m-0">
<CardContent className="h-full flex flex-col flex-1 p-6">
<div className="flex flex-col items-center md:items-center text-center gap-4 md:gap-0 flex-1">
<div className="relative mb-0 md:mb-4 shrink-0">
<div className="w-20 h-20 min-[13.5rem]:w-32 min-[13.5rem]:h-32 rounded-full overflow-hidden bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
<Image
src={imageSrc}
alt={title}
width={128}
height={128}
className="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-300"
/>
</div>
</div>

<div className="flex-1 flex flex-col justify-center mt-2">
<h3 className="font-semibold text-base md:text-lg text-gray-900 mb-1.5 md:mb-2 group-hover:text-ehk-dark-red transition-colors min-[20rem]:hyphens-none break-words">
{title}
</h3>
<p className="text-sm md:text-m text-gray-600 mb-2 md:mb-3">{description}</p>
</div>
</div>
</CardContent>
</Card>
</a>
)

}
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="block h-full w-full max-w-full sm:w-[413px]"
>
{card}
</a>
);
}

return (
<Link href={href} className="block h-full w-full max-w-full sm:w-[413px]">
{card}
</Link>
);
}
Loading