Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/organizer carousel #535

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
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",
}),
],
}),
],
}),
],
});
9 changes: 9 additions & 0 deletions apps/site/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ const nextConfig = {
// },
];
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.sanity.io",
pathname: "/images/**",
},
],
},
};

module.exports = nextConfig;
3 changes: 2 additions & 1 deletion apps/site/src/app/(main)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Landing, ChooseCharacter, FAQ, Sponsors, Partners } from "./sections";
import { Landing, ChooseCharacter, FAQ, Sponsors, Partners, Organizers } from "./sections";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[prettier] reported by reviewdog 🐶

Suggested change
import { Landing, ChooseCharacter, FAQ, Sponsors, Partners, Organizers } from "./sections";
import {
Landing,
ChooseCharacter,
FAQ,
Sponsors,
Partners,
Organizers,
} from "./sections";

export const revalidate = 60;

Expand All @@ -19,6 +19,7 @@ export default function Home() {
<FAQ />
<Sponsors />
<Partners />
<Organizers />
</>
);
}
71 changes: 71 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,71 @@
"use client";

import React, { useEffect, useState } from "react";
import Image from "next/image";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'Image' is defined but never used.

import box from "@/assets/images/center_chat_box.svg";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'box' is defined but never used.

import boxBG from "@/assets/images/center_chat_box_bg.svg";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'boxBG' is defined but never used.

import Link from "next/link";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'Link' is defined but never used.


export const InfiniteMovingCards = ({
items,
direction = "left",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'direction' is assigned a value but never used.

speed = "fast",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'speed' is assigned a value but never used.

pauseOnHover = true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'pauseOnHover' is assigned a value but never used.

}: {
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <@typescript-eslint/no-unused-vars> reported by reviewdog 🐶
'start' is assigned a value but never used.


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

// Clear existing clones
while (scrollerRef.current.children.length > items.length) {
scrollerRef.current.lastChild &&
scrollerRef.current.removeChild(scrollerRef.current.lastChild);
Comment on lines +36 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] <no-unused-expressions> reported by reviewdog 🐶
Expected an assignment or function call and instead saw an expression.

}

// 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,155 @@
"use client";

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

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">
<div
className="relative w-[200px] h-[100px]"
style={{
transform: "skew(20deg)",
}}
>
{/* Background chat box - positioned slightly offset */}
<div className="absolute -right-1 -bottom-1 w-full h-full">
<Image
src={boxBG}
alt="Box Background"
fill
sizes="200px"
loading="lazy"
className="object-contain"
/>
</div>

{/* Main chat box */}
<Image
src={box}
alt="Box"
fill
sizes="200px"
loading="lazy"
className="object-contain"
/>
<div className="absolute w-full h-full 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)" }}
>
<Link
href={item.link}
target="_blank"
className="relative block w-full h-full"
>
<Image
src={item.image}
alt={item.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover opacity-75 group-hover:opacity-100"
/>
</Link>
</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
Loading