Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
a518c50
feat: add CurrentSpeakersHighlight component with carousel for speake…
Francisca105 Feb 2, 2026
dab8f57
refactor: simplify CurrentSpeakersHighlight component by removing sta…
Francisca105 Feb 2, 2026
7375f38
Merge branch 'main' into francisca105/home-speakers
Francisca105 Feb 13, 2026
27cdd31
fix: remove fallback image source from SpeakerCard component
Francisca105 Feb 13, 2026
96ea86d
feat: add navigation links for old and current speakers in CurrentSpe…
Francisca105 Feb 13, 2026
e1a8c91
feat: update link styles and text in CurrentSpeakersHighlight component
Francisca105 Feb 13, 2026
f70bd11
feat: update navigation link for previous speakers in CurrentSpeakers…
Francisca105 Feb 13, 2026
104b7fd
fix: adjust layout structure in CurrentSpeakersPage component
Francisca105 Feb 13, 2026
30b7e22
feat: refactor speaker page layout and enhance session display
Francisca105 Feb 13, 2026
3d06866
feat: enhance speaker page layout and add back button; update mock sp…
Francisca105 Feb 13, 2026
bb12465
refactor: remove cache option from fetch calls in service files
Francisca105 Feb 13, 2026
c064abc
feat: add session details to speaker page and import SessionService
Francisca105 Feb 13, 2026
d1aba18
feat: add dynamic export to CurrentSpeakersHighlight component
Francisca105 Feb 13, 2026
787f84e
feat: integrate ShowMore component for session description truncation
Francisca105 Feb 13, 2026
c1be278
feat: update speaker fetching logic and enhance service revalidation …
Francisca105 Feb 13, 2026
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
12 changes: 7 additions & 5 deletions src/app/(root)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import SpeakersHighlight from "@/components/Home/SpeakersHighlight";
import AboutUs from "@/components/Home/AboutUs";
import SponsorsPartners from "@/components/Home/SponsorsPartners";
import FAQ from "@/components/Home/FAQ";
import CurrentSpeakersHighlight from "@/components/Home/CurrentSpeakersHighlight";

export default async function Root() {
return (
<main className="flex flex-col">
<HeroSection />
<SpeakersHighlight backgroundClass="bg-white" />
<EventSection backgroundClass="bg-sinfo-light" />
<AboutUs backgroundClass="bg-white" />
<SponsorsPartners backgroundClass="bg-sinfo-light" />
<FAQ backgroundClass="bg-white" />
<CurrentSpeakersHighlight backgroundClass="bg-sinfo-light" />
{/* <SpeakersHighlight backgroundClass="bg-white" /> */}
<EventSection backgroundClass="bg-white" />
<AboutUs backgroundClass="bg-sinfo-light" />
<SponsorsPartners backgroundClass="bg-white" />
<FAQ backgroundClass="bg-sinfo-light" />
</main>
);
}
311 changes: 117 additions & 194 deletions src/app/speakers/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from "react";
import Link from "next/link";
import { SpeakerService } from "@/services/SpeakerService";
import {
generateTimeInterval,
getEventDay,
getEventMonth,
} from "@/utils/utils";
import { SessionService } from "@/services/SessionService";
import { generateTimeInterval } from "@/utils/utils";
import ImageWithFallback from "@/components/ImageWithFallback";
import { ShowMore } from "@/components/ShowMore";
import { Calendar, Clock, MapPin } from "lucide-react";

export const dynamic = "force-dynamic";

Expand All @@ -16,22 +15,10 @@ type Props = {
};
};

export async function generateMetadata({ params }: Props) {
const speaker = await SpeakerService.getSpeaker(params.id);
return {
title: speaker ? `${speaker.name} • SINFO` : "Speaker • SINFO",
description: speaker?.description || "Speaker details",
openGraph: speaker
? {
images: speaker.img ? [speaker.img] : undefined,
}
: undefined,
} as any;
}

export default async function Page({ params }: Props) {
const { id } = params;
const speaker = await SpeakerService.getSpeaker(id);
const session = await SessionService.getSessionBySpeaker(id);

if (!speaker) {
return (
Expand All @@ -52,197 +39,133 @@ export default async function Page({ params }: Props) {
);
}

const mainSession = speaker.sessions?.[0];
const sessionTitle = mainSession?.name;
const formattedDate = mainSession?.date
? `${getEventMonth(mainSession.date, false)} ${getEventDay(mainSession.date)}`
: undefined;
const timeRange = mainSession?.date
? generateTimeInterval(mainSession.date, mainSession?.duration ?? 0, {
onlyHours: true,
})
: undefined;
const sessionPlace = mainSession?.place || "Main Stage";

const SpeakerPhoto = ({ className = "" }: { className?: string }) => (
<div className={`relative flex justify-center ${className}`}>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 md:-translate-x-[60%] -translate-y-1/2 w-[340px] h-[340px] md:w-[380px] md:h-[380px] bg-sinfo-quinary rounded-full overflow-hidden shadow-2xl">
<ImageWithFallback
src={speaker.img}
alt={speaker.name}
className="absolute inset-0 w-full h-full object-contain"
sizes="(max-width: 768px) 100vw, 50vw"
width={380}
height={380}
/>
</div>
</div>
);

const SessionInfo = ({ className = "" }: { className?: string }) =>
mainSession ? (
<div className={`text-white ${className}`}>
<p className="font-bold text-xl md:text-2xl">{sessionPlace}</p>
<p className="text-lg md:text-xl">
{formattedDate || "February 17"} • {timeRange || "16h30-17h20"}
</p>
</div>
) : null;

const SessionTitleBox = ({ className = "" }: { className?: string }) =>
sessionTitle ? (
<div
className={`bg-sinfo-primary border-4 border-white rounded-lg p-6 shadow-xl w-fit ${className}`}
>
<p className="text-sinfo-quinary font-black uppercase text-2xl md:text-3xl leading-tight">
{sessionTitle}
</p>
</div>
) : null;

return (
<main className="w-full min-h-screen bg-sinfo-primary relative overflow-hidden">
{/* Decorative circles */}
<div className="absolute left-0 bottom-0 w-28 h-28 md:w-40 md:h-40 bg-sinfo-tertiary rounded-tr-full z-10" />
<div className="absolute right-0 bottom-0 w-28 h-28 md:w-40 md:h-40 bg-sinfo-secondary rounded-tl-full z-10" />

{/* Glowing line separator - positioned where circles end */}
<div className="absolute bottom-0 md:bottom-0 left-0 right-0 z-[5]">
<div className="w-full h-[3px] bg-gradient-to-r from-transparent via-white/50 to-transparent animate-pulse shadow-[0_0_10px_rgba(255,255,255,0.5)]" />
</div>

<div className="relative max-w-7xl mx-auto px-6 md:px-10 py-12 md:py-16">
{/* Top decorative elements - only visible on desktop */}
<div className="hidden md:block absolute top-4 right-8 md:top-6 md:right-16 z-20">
<ImageWithFallback
src="/images/decorative-images/redElement.svg"
alt="Decorative element"
width={80}
height={80}
className="w-16 h-16 md:w-20 md:h-20"
/>
<main className="min-h-screen bg-white">
{/* Hero Section */}
<section className="bg-gradient-to-br from-sinfo-primary via-sinfo-primary to-sinfo-secondary py-16 sm:py-20 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 sm:mb-6">
Speakers
</h1>
<p className="text-base sm:text-lg md:text-xl text-white/90 max-w-3xl mx-auto">
Meet the global voices that are shaping SINFO&apos;s excellence.
</p>
</div>
</div>

{/* Main grid */}
<div className="flex flex-col md:grid md:grid-cols-2 gap-8 md:gap-12 md:items-start">
{/* Mobile layout - custom order */}
<div className="md:hidden space-y-8 relative">
{/* 1. Name and title */}
<div>
<h1 className="text-6xl font-black uppercase leading-[0.85] tracking-tight text-white">
</section>

{/* Main Content */}
<section className="py-12 sm:py-16 md:py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
{/* Left Column - Info */}
<div className="lg:col-span-7 xl:col-span-8 order-2 lg:order-1">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-2">
{speaker.name}
</h1>
{(speaker.title || speaker.company?.name) && (
<p className="mt-4 text-sinfo-tertiary text-xl font-bold">
{speaker.title}
</p>
)}
</div>
<h2 className="text-xl md:text-2xl text-sinfo-primary font-medium mb-8">
{speaker.title}
{speaker.company?.name && (
<span className="text-gray-500">
, {speaker.company.name}
</span>
)}
</h2>

<div className="prose prose-lg text-gray-600 max-w-none mb-12 whitespace-pre-line">
{speaker.description}
</div>

{/* Red element decoration after title, before photo */}
<div className="flex justify-end -mb-14">
<ImageWithFallback
src="/images/decorative-images/redElement.svg"
alt="Decorative element"
width={56}
height={56}
className="w-16 h-16"
/>
{/* Sessions */}
{speaker.sessions && speaker.sessions.length > 0 && (
<div className="border-t border-gray-200 pt-8">
<h3 className="text-2xl font-bold text-sinfo-primary mb-6">
Sessions
</h3>
<div className="space-y-6">
{speaker.sessions.map((session) => {
const dateStr = session.date
? new Date(session.date).toLocaleDateString("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
})
: "";
const timeStr = session.date
? generateTimeInterval(
session.date,
session.duration || 0,
{ onlyHours: true },
)
: "";

return (
<div
key={session.id}
className="bg-gray-50 rounded-lg p-6 hover:shadow-md transition-shadow"
>
<div className="flex flex-wrap gap-y-2 gap-x-6 text-sm font-semibold text-sinfo-secondary mb-3">
{session.date && (
<div className="flex items-center gap-1.5">
<Calendar className="w-4 h-4" />
<span>{dateStr}</span>
</div>
)}
{timeStr && (
<div className="flex items-center gap-1.5">
<Clock className="w-4 h-4" />
<span>{timeStr}</span>
</div>
)}
{session.place && (
<div className="flex items-center gap-1.5">
<MapPin className="w-4 h-4" />
<span>{session.place}</span>
</div>
)}
</div>
<h4 className="text-xl font-bold text-gray-900">
{session.name}
</h4>
{session.description && (
<ShowMore lines={3} className="mt-2 text-gray-600">
{session.description}
</ShowMore>
)}
</div>
);
})}
</div>
</div>
)}
</div>

{/* 2. Photo with decorative elements */}
<div className="space-y-8 relative -mt-14">
{/* Speaker photo with yellow background */}
<SpeakerPhoto className="h-80" />

{/* Small decorative box */}
<div className="max-w-[180px] -mt-9">
{/* Right Column - Image */}
<div className="lg:col-span-5 xl:col-span-4 order-1 lg:order-2">
<div className="relative aspect-square w-full max-w-md mx-auto rounded-full overflow-hidden shadow-xl bg-gray-200">
<ImageWithFallback
src="/images/decorative-images/star.svg"
alt="Decorative star"
width={100}
height={100}
className="w-20 h-20"
src={speaker.img}
alt={speaker.name}
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 33vw"
/>
</div>
</div>

{/* 3. Description box */}
<div className="bg-sinfo-light rounded-lg p-8 text-black flex items-center relative">
<p className="text-base font-bold leading-relaxed whitespace-pre-line">
{speaker.description || "Texto texto bla bla"}
</p>
</div>

{/* 4. Session title box */}
<SessionTitleBox />

{/* 5. Session info */}
<SessionInfo />
</div>

{/* Desktop layout - original two columns */}
<div className="hidden md:block space-y-8">
{/* Name */}
<div>
<h1 className="text-7xl lg:text-8xl font-black uppercase leading-[0.85] tracking-tight text-white">
{speaker.name}
</h1>
{(speaker.title || speaker.company?.name) && (
<p className="mt-4 text-sinfo-tertiary text-2xl font-bold">
{speaker.title}
</p>
)}
</div>

{/* Description box with light background */}
<div className="bg-sinfo-light rounded-lg p-10 text-black flex items-center">
<p className="text-lg font-bold leading-relaxed whitespace-pre-line">
{speaker.description || "Texto texto bla bla"}
</p>
</div>

{/* Session info */}
<SessionInfo />
</div>

{/* Desktop right column - Photo and session title */}
<div className="hidden md:block space-y-8 relative">
{/* Speaker photo with yellow background */}
<SpeakerPhoto className="h-[340px]" />

{/* Decorative plus */}
<div className="absolute right-8 top-1/2 w-20 h-20">
<span className="absolute left-1/2 top-0 bottom-0 w-[4px] bg-white -translate-x-1/2" />
<span className="absolute top-1/2 left-0 right-0 h-[4px] bg-white -translate-y-1/2" />
</div>

{/* Small decorative box */}
<div className="max-w-[180px] -mt-9">
<ImageWithFallback
src="/images/decorative-images/star.svg"
alt="Decorative star"
width={100}
height={100}
className="w-24 h-24"
/>
</div>

{/* Session title box */}
<SessionTitleBox />
{/* Back Button */}
<div className="mt-12 text-center">
<Link
href="/speakers"
className="inline-flex items-center px-6 py-3 bg-sinfo-primary text-white rounded-lg font-semibold hover:opacity-95 transition"
>
&larr; Back to all speakers
</Link>
</div>
</div>

{/* Bottom CTA button */}
<div className="mt-12 md:mt-16 flex justify-center relative z-10">
<Link
href="/speakers"
className="bg-sinfo-senary text-white font-black py-5 px-16 md:py-6 md:px-20 rounded-full text-xl md:text-2xl uppercase shadow-2xl hover:opacity-90 transition-opacity"
>
SEE ALL SPEAKERS
</Link>
</div>
</div>
</section>
</main>
);
}
Loading