Skip to content
Merged
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
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import React from "react";
import PrivacyPolicy from "@/pages/PrivacyPolicy.tsx";
import Footer from "@/components/Footer.tsx";
import SpeakerInfo from "@/components/SpeakerInfo.tsx";
import {AppProvider} from "@/context/AppContext.tsx";

const queryClient = new QueryClient();

const App = () => {
return (
<QueryClientProvider client={queryClient}>
<AppProvider>
<TooltipProvider>
<Toaster />
<Sonner />
Expand All @@ -28,12 +30,13 @@ const App = () => {
<Route path="*" element={<NotFound />} />
<Route path="/code-of-conduct" element={<CodeOfConduct />}/>
<Route path="/privacy-policy" element={<PrivacyPolicy/>}/>
<Route path="/speaker" element={<SpeakerInfo/>}/>
<Route path="/speaker/:speakerId" element={<SpeakerInfo/>}/>
</Routes>
</main>
<Footer />
</BrowserRouter>
</TooltipProvider>
</AppProvider>
</QueryClientProvider>
)
};
Expand Down
44 changes: 23 additions & 21 deletions src/components/SocialMedia.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,45 @@ interface SocialMediaProps {
className?: string
}

const getSVG = (link: ILink, className) => {
const SocialMedia: React.FC<SocialMediaProps> = ({link, className}) => {
switch (link.linkType) {
case LinkType.Instagram:
return (
<Instagram href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>Instagram</title>
</Instagram>
<a href={link.url} target="_blank" rel="noopener noreferrer" className={`hover:text-alternative-400 transition-colors ${className}`}>
<Instagram href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>Instagram</title>
</Instagram>
</a>

);
case LinkType.Twitter:
return (
<Twitter href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>Twitter</title>
</Twitter>
<a href={link.url} target="_blank" rel="noopener noreferrer" className={`hover:text-alternative-400 transition-colors ${className}`}>

<Twitter href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>Twitter</title>
</Twitter>
</a>
);
case LinkType.LinkedIn:
return (
<Linkedin href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>LinkedIn</title>
</Linkedin>
<a href={link.url} target="_blank" rel="noopener noreferrer" className={`hover:text-alternative-400 transition-colors ${className}`}>
<Linkedin href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>LinkedIn</title>
</Linkedin>
</a>
);
case LinkType.GitHub:
return (
<Github href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>GitHub</title>
</Github>
<a href={link.url} target="_blank" rel="noopener noreferrer" className={`hover:text-alternative-400 transition-colors ${className}`}>
<Github href={link.url} target="_blank" height={26} width={26} className={`hover:underline ${className}`}>
<title>GitHub</title>
</Github>
</a>
);
default:
return null;
}
}

const SocialMedia: React.FC<SocialMediaProps> = ({link, className}) => {
return <a href={link.url} target="_blank" rel="noopener noreferrer" className={`hover:text-alternative-400 transition-colors ${className}`}>
{getSVG(link, className)}
</a>


};

export default SocialMedia;
9 changes: 6 additions & 3 deletions src/components/Speaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ interface SpeakerProps {
const Speaker: React.FC<SpeakerProps> = ({speaker}) => {
const navigate = useNavigate();

const handleClick = (speaker: ISpeaker) => {
navigate(`/speaker/${speaker.id}`, {state: {speaker}});
}

return (
<article className="min-w-96 h-full mx-1 flex flex-col justify-center items-center">
<img src={speaker.profilePicture} alt={speaker.fullName} className="h-60 w-60 rounded-3xl" loading="lazy"/>
<Card className="bg-white/10 border-0 shadow-none text-white flex flex-col justify-start h-44 p-6 w-full">
<p className="text-2xl font-bold">{`${speaker.firstName.split(" ")[0]} ${speaker.lastName.split(" ")[0]}`}</p>
<p className="py-2 text-xl line-clamp-2 my-4 rounded">{speaker.tagLine}</p>
<div className="flex items-center gap-6 text-alternative-100">
<div className="flex items-center justify-start gap-6 text-alternative-100">
{speaker.links.map((link, index) => (
<SocialMedia key={`link-${link.url}`} link={link} />
))}
<button role="link" className="hidden justify-center items-center rounded-lg py-1 px-1">
<button onClick={() => handleClick(speaker)} role="link" className="flex hover:text-alternative-400 rounded-lg">
Ver más
</button>
</div>

</Card>
</article>
)
Expand Down
48 changes: 46 additions & 2 deletions src/components/SpeakerInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
import {ISpeaker} from "@/types/speakers.ts";
import {Card, CardFooter} from "@/components/ui/card.tsx";
import React, {useEffect} from "react";
import {useLocation} from "react-router-dom";
import SocialMedia from "@/components/SocialMedia.tsx";
import {Badge} from "@/components/ui/badge.tsx";
import {Share2, Speech} from "lucide-react";
import Gradient from "@/components/Gradient.tsx";

const SpeakerInfo = () => {
const location = useLocation();
const speaker = location.state?.speaker as ISpeaker;
const {fullName, sessions, profilePicture, tagLine, bio, links} = speaker;

return (
<section>
<Gradient className="py-10 px-20" >
<article className="flex flex-col gap-8">
<Card className="flex gap-6 border-0 shadow-none text-primary-600">
<img src={profilePicture} alt={fullName} className="w-60 h-60 rounded-xl"/>
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold text-alternative-700">{fullName}</h1>
<h2 className="text-xl">{tagLine}</h2>
<p className="text-gray-700">{bio}</p>
<div className="flex items-center justify-start gap-4 text-gray-700">
{links.map((link, index) => (
<SocialMedia key={`link-${link.url}`} className="h-6 w-6 hover:text-primary-600" link={link} />
))}
<Share2 className="hidden h-6 w-6 hover:text-primary-600">
<title>Compartir</title>
</Share2>
</div>
</div>
</Card>

</section>
<Card className="py-8 px-6 flex flex-col gap-4">
<h2 className="text-3xl font-bold text-alternative-700">
Charla
</h2>
<h3 className="text-2xl text-gray-700 font-semibold">
{sessions.title}
</h3>
<span className="text-lg text-primary-600 font-semibold">Descripción</span>
<p className="text-lg text-gray-700">{sessions.description}</p>
<CardFooter className="p-0">
<Badge variant="default" className="flex gap-2"><Speech/> Track: {speaker.category}</Badge>
</CardFooter>
</Card>
</article>
</Gradient>
)
}
export default SpeakerInfo
19 changes: 4 additions & 15 deletions src/components/Speakers.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import React, {useEffect, useState} from 'react';
import {ISpeaker} from "@/types/speakers.ts";
import {getSpeakers} from "@/https/fetch.ts";
import React from 'react';
import Speaker from "@/components/Speaker.tsx";
import Carousel, {GridConfig} from "@/components/Carousel.tsx";
import Loading from "@/components/Loading.tsx";
import {useAppContext} from "@/context/AppContext.tsx";

const Speakers = () => {
const [speakers, setSpeakers] = useState<ISpeaker[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const {speakers} = useAppContext();

const gridLg: GridConfig = {cols: 3, rows: 2, itemsPerSlide: 5};
const gridMd: GridConfig = {cols: 2, rows: 2, itemsPerSlide: 3};
const gridSm: GridConfig = {cols: 1, rows: 1, itemsPerSlide: 1};

useEffect(() => {
getSpeakers().then((data) => {
setSpeakers(data);
setLoading(false);
});
}, []);
if (loading) return (<Loading size={60} gap={4} count={5} />);

if (speakers.length === 0) return null

return (
Expand All @@ -31,7 +20,7 @@ const Speakers = () => {
aria-labelledby="hero-title"
>
<h1 className="text-5xl font-bold text-white">Speakers</h1>
<div className="mt-8 md:w-60 w-24 h-1 bg-gradient-to-r from-posadev-darkPink to-posadev-brightPink mx-auto rounded-full"></div>
<div className="mt-8 md:w-60 w-24 h-1 bg-candy-stripes mx-auto rounded-full"></div>
<section className="py-20 w-full max-w-7xl h-full">
<Carousel gridMd={gridMd} gridSm={gridSm} gridLg={gridLg} hideArrows items={speakers} renderItem={(speaker) => <Speaker key={speaker.id} speaker={speaker} /> } />
</section>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const badgeVariants = cva(
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
"border-transparent bg-primary text-primary-foreground",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
Expand Down
62 changes: 62 additions & 0 deletions src/context/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createContext, useContext, useState, useEffect } from "react";
import {ISession, ISpeaker} from "@/types/speakers.ts";
import {getAll} from "@/https/fetch.ts";
import {AppStatus} from "@/types/types.ts";
import Loading from "@/components/Loading.tsx";
import {addSessionSpeakers} from "@/lib/utils.ts";


interface AppContextType {
speakers: ISpeaker[];
sessions: ISession[];
agenda: any[]; // o tipa tu modelo de agenda si lo tienes
appStatus: AppStatus;
}

const AppContext = createContext(null);

export const AppProvider = ({ children }) => {
const [speakers, setSpeakers] = useState<ISpeaker[]>([]);
const [agenda, setAgenda] = useState([]);
const [appStatus, setAppStatus] = useState(AppStatus.Loading);

useEffect(() => {
getAll().then((data) => {
const getSpeakersWithSessions = addSessionSpeakers(data.sessions, data.speakers, data.categories[0].items);
setSpeakers(getSpeakersWithSessions);
});
setAppStatus(AppStatus.Success)
}, []);

const value = {
speakers,
agenda,
appStatus
};

if (appStatus === AppStatus.Loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<Loading size={60} gap={4} count={5} />
</div>
);
}

if (appStatus === AppStatus.Error) {
return (
<div className="flex items-center justify-center min-h-screen text-red-500">
Error al cargar los datos iniciales 😞
</div>
);
}

return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

export const useAppContext = (): AppContextType => {
const context = useContext(AppContext);
if (!context) {
throw new Error("");
}
return context;
};
10 changes: 9 additions & 1 deletion src/https/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ISpeaker} from "@/types/speakers.ts";
import {IConferenceData, ISpeaker} from "@/types/speakers.ts";

export const getSpeakers = async () => {
const response = await fetch('https://sessionize.com/api/v2/n25df8kw/view/Speakers');
Expand All @@ -7,4 +7,12 @@ export const getSpeakers = async () => {
}
const data: ISpeaker[] = await response.json();
return data;
}

export const getAll = async (): Promise<IConferenceData> => {
const response = await fetch('https://sessionize.com/api/v2/n25df8kw/view/All');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return await response.json()
}
12 changes: 11 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ All colors MUST be HSL.
}
}

/* Estilos personalizados para Posadev */
.gradient-bg {
background: linear-gradient(
125deg,
Expand All @@ -149,6 +148,7 @@ All colors MUST be HSL.
}
}


.gradient-text {
background: linear-gradient(135deg, #CF0F47, #FF0B55);
-webkit-background-clip: text;
Expand Down Expand Up @@ -181,4 +181,14 @@ All colors MUST be HSL.
30% { transform: translateY(-8px) rotate(-18deg); }
60% { transform: translateY(0) rotate(-22deg); }
100% { transform: translateY(0) rotate(-20deg); }
}

@layer utilities {
.bg-candy-stripes {
background-image: repeating-linear-gradient(
45deg,
#FF0B55 0 10px,
white 10px 20px
);
}
}
16 changes: 15 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import {ICategory, ISession, ISpeaker} from "@/types/speakers.ts";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
Expand All @@ -16,4 +17,17 @@ export const handleCopy = async (toast, dismiss) => {
} finally {
setTimeout(() => dismiss(), 3000);
}
};
};

export const addSessionSpeakers = (sessions: ISession[], speakers: ISpeaker[], categories: ICategory[]): ISpeaker[] => {
return speakers.map((speaker) => {
const session = sessions.find((session) => session.speakers.includes(speaker.id));
const category = categories.find((category) => session.categoryItems.includes(category.id))
console.log(category.name)
return {
...speaker,
sessions: session,
category: category.name
};
});
}
2 changes: 0 additions & 2 deletions src/pages/Index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React, {useEffect} from 'react';
import Hero from '@/components/Hero';
import Gallery from '@/components/Gallery';
import Sponsors from '@/components/Sponsors';
import {useLocation} from "react-router-dom";
import Communities from "@/components/Communities.tsx";
import BecomeSponsor from "@/components/BecomeSponsor.tsx";
import Organizers from "@/components/Organizers.tsx";
import Speakers from "@/components/Speakers.tsx";
Expand Down
Loading