Skip to content

Commit

Permalink
Feat/organizer carousel (#535)
Browse files Browse the repository at this point in the history
* add placeholder organizer section

* change links

* sanity retreival

* build issues

* Fix issues with loading

* commit before merge

* Utilize correct Image and img for images in organizer carosel

* fix sanity cors

* remove unused imports/props

* disable eslint

---------

Co-authored-by: Albert Wang <[email protected]>
  • Loading branch information
pandyrew and waalbert authored Jan 26, 2025
1 parent 11afeeb commit 685d786
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 4 deletions.
2 changes: 2 additions & 0 deletions apps/sanity/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import resource from "./resource";
import resourceCategory from "./resourceCategory";
import resourceCategoryOrder from "./resourceCategoryOrder";
import sponsors from "./sponsors";
import organizers from "./organizers";

export const schemaTypes = [
faqs,
Expand All @@ -14,4 +15,5 @@ export const schemaTypes = [
resourceCategory,
resourceCategoryOrder,
sponsors,
organizers,
];
73 changes: 73 additions & 0 deletions apps/sanity/schemas/organizers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { defineType, defineField, defineArrayMember } from "sanity";
import { Users } from "lucide-react";

export default defineType({
name: "organizers",
title: "Organizers",
icon: Users,
type: "document",
fields: [
defineField({
name: "organizers",
title: "Organizers",
type: "array",
of: [
defineArrayMember({
type: "object",
name: "organizer",
fields: [
defineField({
name: "name",
title: "Name",
type: "string",
validation: (Rule) => Rule.required(),
}),
defineField({
name: "department",
title: "Department",
type: "string",
validation: (Rule) => Rule.required(),
options: {
list: [
{ title: "Tech", value: "Tech" },
{ title: "Marketing", value: "Marketing" },
{ title: "Logistics", value: "Logistics" },
{ title: "Corporate", value: "Corporate" },
{ title: "Graphics", value: "Graphics" },
],
},
}),
defineField({
name: "role",
title: "Role",
type: "string",
validation: (Rule) => Rule.required(),
options: {
list: [
{ title: "Director", value: "Director" },
{ title: "Organizer", value: "Organizer" },
{ title: "Intern", value: "Intern" },
{ title: "Advisor", value: "Advisor" },
],
},
}),
defineField({
name: "image",
title: "Profile Image",
type: "image",
options: {
hotspot: true,
},
}),
defineField({
name: "link",
title: "Social Link",
type: "url",
description: "LinkedIn or other social media profile link",
}),
],
}),
],
}),
],
});
13 changes: 9 additions & 4 deletions apps/site/src/app/(main)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Landing, ChooseCharacter, FAQ, Sponsors, Partners } from "./sections";
import {
Landing,
ChooseCharacter,
FAQ,
Sponsors,
Partners,
Organizers,
} from "./sections";

export const revalidate = 60;

Expand All @@ -8,9 +15,6 @@ export default function Home() {
return process.env.MAINTENANCE_MODE_HOME ? (
<>
<Landing />
<ChooseCharacter />
<FAQ />
<Sponsors />
</>
) : (
<>
Expand All @@ -19,6 +23,7 @@ export default function Home() {
<FAQ />
<Sponsors />
<Partners />
<Organizers />
</>
);
}
66 changes: 66 additions & 0 deletions apps/site/src/app/(main)/(home)/sections/InfiniteMovingCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";

import React, { useEffect, useState } from "react";

export const InfiniteMovingCards = ({
items,
}: {
items: {
quote: string;
name: string;
title: string;
image: string;
link: string;
}[];
direction?: "left" | "right";
speed?: "fast" | "normal" | "slow";
pauseOnHover?: boolean;
className?: string;
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const scrollerRef = React.useRef<HTMLUListElement>(null);
const [start, setStart] = useState(false);

useEffect(() => {
if (!containerRef.current || !scrollerRef.current) return;

// Clear existing clones

while (scrollerRef.current.children.length > items.length) {
/* eslint-disable-next-line no-unused-expressions*/
scrollerRef.current.lastChild &&
scrollerRef.current.removeChild(scrollerRef.current.lastChild);
}

// Clone items just once for infinite scroll
const originalItems = Array.from(scrollerRef.current.children).slice(
0,
items.length,
);
originalItems.forEach((item) => {
const duplicatedItem = item.cloneNode(true);
if (scrollerRef.current) {
scrollerRef.current.appendChild(duplicatedItem);
}
});

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setStart(true);
}
});
},
{ threshold: 0.1 },
);

observer.observe(containerRef.current);

return () => {
observer.disconnect();
};
}, [items]);

// ... rest of the component stays the same ...
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"use client";

import React, { useEffect, useState } from "react";
import box from "@/assets/images/center_chat_box.svg";
import boxBG from "@/assets/images/center_chat_box_bg.svg";
import Image from "next/image";

export const InfiniteMovingCards = ({
items,
direction = "left",
speed = "fast",
pauseOnHover = true,
}: {
items: {
quote: string;
name: string;
title: string;
image: string;
link: string;
}[];
direction?: "left" | "right";
speed?: "fast" | "normal" | "slow";
pauseOnHover?: boolean;
className?: string;
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const scrollerRef = React.useRef<HTMLUListElement>(null);
const [start, setStart] = useState(false);

useEffect(() => {
if (!containerRef.current || !scrollerRef.current) return;

// Clone items multiple times to ensure smooth infinite scroll
const scrollerContent = Array.from(scrollerRef.current.children);
// Clone enough times to ensure continuous scroll
scrollerContent.forEach((item) => {
const duplicatedItem = item.cloneNode(true);
if (scrollerRef.current) {
scrollerRef.current.appendChild(duplicatedItem);
}
});

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setStart(true);
}
});
},
{ threshold: 0.1 },
);

observer.observe(containerRef.current);

return () => {
observer.disconnect();
};
}, [items]);

const duration =
speed === "fast" ? "30s" : speed === "normal" ? "45s" : "270s";

return (
<div
ref={containerRef}
className="scroller relative z-[20] max-w-3xl overflow-hidden [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)] pt-28"
style={
{
"--duration": duration,
} as React.CSSProperties
}
>
<ul
ref={scrollerRef}
className={`flex min-w-full shrink-0 gap-2 py-4 w-max flex-nowrap items-center justify-center ${
start ? "animate-scroll" : ""
} ${pauseOnHover ? "hover:[animation-play-state:paused]" : ""} ${
direction === "right" ? "[animation-direction:reverse]" : ""
}`}
>
{items.map((item, idx) => (
<li
key={`${item.name}-${idx}`}
className="relative flex-shrink-0 group"
style={{ transform: "skew(-20deg)" }}
>
{/* Info popup on hover */}
<div className="absolute -top-[110px] left-[12%] -translate-x-1/2 opacity-0 group-hover:opacity-100 z-[300] pointer-events-none transition-opacity">
<div
className="relative w-[200px] h-[100px]"
style={{
transform: "skew(20deg)",
}}
>
{/* Background chat box - positioned slightly offset */}
<div className="absolute inset-0 -right-1 -bottom-1">
<Image
src={boxBG}
alt="Box Background"
className="w-full h-full object-contain"
style={{ maxWidth: "100%" }}
/>
</div>

{/* Main chat box */}
<div className="absolute inset-0">
<Image
src={box}
alt="Box"
className="w-full h-full object-contain"
style={{ maxWidth: "100%" }}
/>
</div>

<div className="absolute inset-0 flex flex-col items-center justify-center gap-0">
<p className="text-sm font-medium text-gray-200 leading-tight">
{item.name}
</p>
<p className="text-xs text-gray-400 leading-tight">
{item.title}
</p>
</div>
</div>
</div>

{/* Card */}
<div className="relative w-16 h-20 bg-black border-2 border-white overflow-hidden transition-transform group-hover:scale-105 group-hover:bg-[#006FB2]">
<div
className="absolute inset-0 bg-black group-hover:bg-[#006FB2]"
style={{ transform: "skew(20deg) scale(1.2)" }}
>
<a
href={item.link}
target="_blank"
className="relative block w-full h-full"
>
{/* eslint-disable-next-line @next/next/no-img-element*/}
<img
src={item.image}
alt={item.name}
className="object-cover w-full h-full opacity-75 group-hover:opacity-100"
style={{ maxWidth: "100%" }}
/>
</a>
</div>
</div>
</li>
))}
</ul>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@keyframes scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(-50%));
}
}
Loading

0 comments on commit 685d786

Please sign in to comment.