feat(ui): add Team block to about pages and fix Affiliation link
This commit is contained in:
@@ -13,7 +13,7 @@ const iconMap = {
|
||||
landmark: Landmark,
|
||||
}
|
||||
|
||||
export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description, logo, linkText, trustPoints }) => {
|
||||
export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description, logo, linkText, linkUrl, trustPoints }) => {
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-background border-t border-border overflow-hidden">
|
||||
<div className="container-premium">
|
||||
@@ -32,10 +32,16 @@ export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description,
|
||||
<img src={logo.url} alt={logo.alt || "Logo stowarzyszenia"} className="max-w-full h-auto grayscale hover:grayscale-0 transition-all duration-300" />
|
||||
</div>
|
||||
)}
|
||||
{linkText && (
|
||||
<Link href="/stowarzyszenie" className="text-accent font-semibold text-lg hover:text-accent-hover underline underline-offset-4">
|
||||
{linkText && linkUrl && (
|
||||
linkUrl.startsWith('#') ? (
|
||||
<a href={linkUrl} className="text-accent font-semibold text-lg hover:text-accent-hover underline underline-offset-4">
|
||||
{linkText}
|
||||
</a>
|
||||
) : (
|
||||
<Link href={linkUrl} className="text-accent font-semibold text-lg hover:text-accent-hover underline underline-offset-4">
|
||||
{linkText}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,5 +26,6 @@ export const Affiliation: Block = {
|
||||
],
|
||||
},
|
||||
{ name: 'linkText', type: 'text', localized: true },
|
||||
{ name: 'linkUrl', type: 'text', localized: true },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FinalCTABlock } from './FinalCTA/Component'
|
||||
import { PageHeaderBlock } from './PageHeader/Component'
|
||||
import { ContactInfoBlock } from './ContactInfo/Component'
|
||||
import { BioBlock } from './Bio/Component'
|
||||
import { TeamBlock } from './Team/Component'
|
||||
import { ServicesDetailedBlock } from './ServicesDetailed/Component'
|
||||
import { EligibilityBlock } from './Eligibility/Component'
|
||||
import { StatsBlock } from './Stats/Component'
|
||||
@@ -23,6 +24,7 @@ const blockComponents = {
|
||||
pageHeader: PageHeaderBlock,
|
||||
contactInfo: ContactInfoBlock,
|
||||
bio: BioBlock,
|
||||
team: TeamBlock,
|
||||
servicesDetailed: ServicesDetailedBlock,
|
||||
eligibility: EligibilityBlock,
|
||||
hero: HeroBlock,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import type { Page, Media } from '../../payload-types'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'team' }>
|
||||
|
||||
export const TeamBlock: React.FC<Props> = ({ members }) => {
|
||||
if (!members || members.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-background border-y border-border overflow-hidden">
|
||||
<div className="container-premium">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-12"
|
||||
>
|
||||
{members.map((member: NonNullable<Props['members']>[0], index: number) => {
|
||||
const photo = typeof member.photo === 'object' && member.photo !== null ? (member.photo as Media) : null
|
||||
const initial = member.name ? member.name.charAt(0).toUpperCase() : ''
|
||||
|
||||
return (
|
||||
<div key={member.id || index} className="flex flex-col bg-card border border-border rounded-none overflow-hidden hover:border-accent/50 transition-colors h-full">
|
||||
<div className="relative aspect-square w-full bg-border/30 flex items-center justify-center shrink-0">
|
||||
{photo && photo.url ? (
|
||||
<Image
|
||||
src={photo.url}
|
||||
alt={photo.alt || member.name}
|
||||
fill
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
className="object-cover grayscale hover:grayscale-0 transition-all duration-700"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center bg-accent text-background font-heading text-6xl font-bold">
|
||||
{initial}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-8 flex flex-col flex-1">
|
||||
<h3 className="font-heading text-2xl font-bold text-foreground mb-1">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-accent font-semibold text-sm uppercase tracking-wide mb-6">
|
||||
{member.role}
|
||||
</p>
|
||||
|
||||
{member.description && (
|
||||
<p className="text-foreground/75 leading-relaxed mb-8 flex-1">
|
||||
{member.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{member.bookingUrl && (
|
||||
<div className="mt-auto pt-4 border-t border-border">
|
||||
<Link
|
||||
href={member.bookingUrl}
|
||||
className="inline-flex items-center text-sm font-semibold text-foreground hover:text-accent uppercase tracking-wider transition-colors"
|
||||
>
|
||||
Umów się
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Block } from 'payload'
|
||||
|
||||
export const Team: Block = {
|
||||
slug: 'team',
|
||||
fields: [
|
||||
{
|
||||
name: 'members',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
maxRows: 20,
|
||||
fields: [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'role', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'photo', type: 'upload', relationTo: 'media' },
|
||||
{ name: 'bookingUrl', type: 'text' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { Eligibility } from '../blocks/Eligibility/config'
|
||||
import { Stats } from '../blocks/Stats/config'
|
||||
import { TestimonialsGrid } from '../blocks/TestimonialsGrid/config'
|
||||
import { Bio } from '../blocks/Bio/config'
|
||||
import { Team } from '../blocks/Team/config'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
@@ -43,6 +44,7 @@ export const Pages: CollectionConfig = {
|
||||
PageHeader,
|
||||
ContactInfo,
|
||||
Bio,
|
||||
Team,
|
||||
ServicesDetailed,
|
||||
Eligibility,
|
||||
Hero,
|
||||
|
||||
@@ -246,6 +246,21 @@ export interface Page {
|
||||
blockName?: string | null;
|
||||
blockType: 'bio';
|
||||
}
|
||||
| {
|
||||
members?:
|
||||
| {
|
||||
name: string;
|
||||
role: string;
|
||||
description?: string | null;
|
||||
photo?: (number | null) | Media;
|
||||
bookingUrl?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'team';
|
||||
}
|
||||
| {
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
@@ -385,6 +400,7 @@ export interface Page {
|
||||
}[]
|
||||
| null;
|
||||
linkText?: string | null;
|
||||
linkUrl?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'affiliation';
|
||||
@@ -653,6 +669,22 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
team?:
|
||||
| T
|
||||
| {
|
||||
members?:
|
||||
| T
|
||||
| {
|
||||
name?: T;
|
||||
role?: T;
|
||||
description?: T;
|
||||
photo?: T;
|
||||
bookingUrl?: T;
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
servicesDetailed?:
|
||||
| T
|
||||
| {
|
||||
@@ -814,6 +846,7 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
};
|
||||
linkText?: T;
|
||||
linkUrl?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user