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
20 changes: 2 additions & 18 deletions components/drops/view/BoostedDropCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { buildProcessedContent } from "@/components/waves/drops/media-utils";
import type { ApiDrop } from "@/generated/models/ApiDrop";
import { convertApiDropToExtendedDrop } from "@/helpers/waves/drop.helpers";
import { useDropBoostMutation } from "@/hooks/drops/useDropBoostMutation";
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import Image from "next/image";
import { memo, useCallback, useContext, useMemo } from "react";

Expand Down Expand Up @@ -70,14 +71,12 @@ const BoostedDropCard = memo(
tabIndex={0}
>
<div className="tw-flex tw-items-center tw-gap-x-3">
{/* Rank badge */}
<div className="tw-flex tw-size-6 tw-flex-shrink-0 tw-items-center tw-justify-center tw-rounded-full tw-bg-amber-600/20 tw-ring-1 tw-ring-amber-500/40">
<span className="tw-text-xs tw-font-bold tw-text-amber-400">
{rank}
</span>
</div>

{/* Author avatar */}
<div className="tw-relative tw-size-8 tw-flex-shrink-0 tw-overflow-hidden tw-rounded-lg tw-bg-iron-900">
{resolvedPfp ? (
<Image
Expand All @@ -92,7 +91,6 @@ const BoostedDropCard = memo(
)}
</div>

{/* Content */}
<div className="tw-min-w-0 tw-flex-1">
<div className="tw-flex tw-items-center tw-gap-x-2">
<UserCICAndLevel
Expand All @@ -109,7 +107,6 @@ const BoostedDropCard = memo(
/>
</div>

{/* Boost count - clickable toggle */}
<button
onClick={handleBoostClick}
disabled={!canBoost || isPending}
Expand All @@ -129,20 +126,7 @@ const BoostedDropCard = memo(
</span>
</button>

{/* Arrow indicator */}
<svg
className="tw-size-4 tw-flex-shrink-0 tw-text-iron-500 tw-transition-colors group-hover:tw-text-amber-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
<ChevronRightIcon className="tw-size-4 tw-flex-shrink-0 tw-text-iron-500 tw-transition-colors group-hover:tw-text-amber-400" />
</div>
</div>
);
Expand Down
40 changes: 3 additions & 37 deletions components/home/HomePageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,18 @@
"use client";

import { useState, useRef, useCallback } from "react";
import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import SubmissionCarousel, {
type SubmissionCarouselHandle,
} from "./carousel/SubmissionCarousel";
import CarouselActiveItemVote from "./carousel/CarouselActiveItemVote";
import CarouselActiveItemDetails from "./carousel/CarouselActiveItemDetails";
import SubmissionCarouselSection from "./SubmissionCarouselSection";
import { NowMintingSection } from "./now-minting";
import { NextMintLeadingSection } from "./next-mint-leading";
import { BoostedSection } from "./boosted";
import { ExploreWavesSection } from "./explore-waves";
import { CarouselHeader, HeroHeader } from "./hero";
import { HeroHeader } from "./hero";

export default function HomePageContent() {
const [activeDrop, setActiveDrop] = useState<ExtendedDrop | null>(null);
const carouselRef = useRef<SubmissionCarouselHandle>(null);

const pauseAutoplay = useCallback(
() => carouselRef.current?.pauseAutoplay(),
[]
);
const resumeAutoplay = useCallback(
() => carouselRef.current?.resumeAutoplay(),
[]
);

return (
<div className="tw-overflow-x-hidden tw-border-y-0 tw-border-l-0 tw-border-r tw-border-solid tw-border-iron-900">
<HeroHeader />
<NowMintingSection />
<section
className="tw-grid tw-h-auto tw-grid-rows-[auto_1fr_auto] sm:tw-h-[100svh] [@media(min-height:1200px)]:tw-h-auto [@media(min-height:1200px)]:tw-grid-rows-[auto_auto_auto] [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-h-auto [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-grid-rows-[auto_auto_auto]"
onMouseEnter={pauseAutoplay}
onMouseLeave={resumeAutoplay}
>
<CarouselHeader />
<div className="tw-min-h-0 [@media(min-height:1200px)]:tw-max-h-[800px] [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-h-[min(70svh,600px)]">
<SubmissionCarousel
ref={carouselRef}
onActiveDropChange={setActiveDrop}
/>
</div>
{activeDrop && <CarouselActiveItemVote drop={activeDrop} />}
</section>
<div onMouseEnter={pauseAutoplay} onMouseLeave={resumeAutoplay}>
<CarouselActiveItemDetails drop={activeDrop} />
</div>
<SubmissionCarouselSection />
<div className="tw-mt-10 tw-border tw-border-x-0 tw-border-b-0 tw-border-t tw-border-solid tw-border-iron-800 tw-pt-10 md:tw-mt-16 md:tw-pt-16">
<NextMintLeadingSection />
<BoostedSection />
Expand Down
94 changes: 94 additions & 0 deletions components/home/SubmissionCarouselSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"use client";

import { useCallback, useRef, useState } from "react";
import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import CarouselActiveItemDetails from "./carousel/CarouselActiveItemDetails";
import CarouselActiveItemVote from "./carousel/CarouselActiveItemVote";
import SubmissionCarousel, {
type SubmissionCarouselHandle,
} from "./carousel/SubmissionCarousel";
import { CarouselHeader } from "./hero";

export default function SubmissionCarouselSection() {
const [activeDrop, setActiveDrop] = useState<ExtendedDrop | null>(null);
const carouselRef = useRef<SubmissionCarouselHandle>(null);
const isPointerInsideRef = useRef(false);
const isVotingModalOpenRef = useRef(false);
const isAutoplayPausedRef = useRef(false);

const updateAutoplayState = useCallback(() => {
const shouldPause =
isPointerInsideRef.current || isVotingModalOpenRef.current;
if (shouldPause === isAutoplayPausedRef.current) {
return;
}
isAutoplayPausedRef.current = shouldPause;
if (shouldPause) {
carouselRef.current?.pauseAutoplay();
} else {
carouselRef.current?.resumeAutoplay();
}
}, []);

const handlePointerEnter = useCallback(() => {
isPointerInsideRef.current = true;
updateAutoplayState();
}, [updateAutoplayState]);

const handlePointerMove = useCallback(() => {
if (isPointerInsideRef.current) {
return;
}
isPointerInsideRef.current = true;
updateAutoplayState();
}, [updateAutoplayState]);

const handlePointerLeave = useCallback(() => {
isPointerInsideRef.current = false;
updateAutoplayState();
}, [updateAutoplayState]);

const handleVoteOpen = useCallback(() => {
isVotingModalOpenRef.current = true;
updateAutoplayState();
}, [updateAutoplayState]);

const handleVoteClose = useCallback(() => {
isVotingModalOpenRef.current = false;
updateAutoplayState();
}, [updateAutoplayState]);

const handleCarouselRef = useCallback(
(instance: SubmissionCarouselHandle | null) => {
carouselRef.current = instance;
updateAutoplayState();
},
[updateAutoplayState]
);

return (
<div
onPointerEnter={handlePointerEnter}
onPointerMove={handlePointerMove}
onPointerLeave={handlePointerLeave}
>
<section className="tw-grid tw-h-auto tw-grid-rows-[auto_1fr_auto] sm:tw-h-[100svh] [@media(min-height:1200px)]:tw-h-auto [@media(min-height:1200px)]:tw-grid-rows-[auto_auto_auto] [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-h-auto [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-grid-rows-[auto_auto_auto]">
<CarouselHeader />
<div className="tw-min-h-0 [@media(min-height:1200px)]:tw-max-h-[800px] [@media(min-width:768px)_and_(orientation:portrait)_and_(max-height:1199px)]:tw-h-[min(70svh,600px)]">
<SubmissionCarousel
ref={handleCarouselRef}
onActiveDropChange={setActiveDrop}
/>
</div>
{activeDrop && (
<CarouselActiveItemVote
drop={activeDrop}
onVoteOpen={handleVoteOpen}
onVoteClose={handleVoteClose}
/>
)}
</section>
<CarouselActiveItemDetails drop={activeDrop} />
</div>
);
}
6 changes: 5 additions & 1 deletion components/home/boosted/BoostedDropCardHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ContentDisplay from "@/components/waves/drops/ContentDisplay";
import { buildProcessedContent } from "@/components/waves/drops/media-utils";
import type { ApiDrop } from "@/generated/models/ApiDrop";
import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers";
import { LinkIcon } from "@heroicons/react/24/outline";
import { LinkIcon, Square2StackIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { memo, useCallback, useMemo } from "react";

Expand Down Expand Up @@ -71,6 +71,10 @@ const BoostedDropCardHome = memo(
<div className="tw-relative tw-aspect-[4/5] tw-w-full tw-overflow-hidden tw-rounded-xl sm:tw-aspect-[3/4]">
{media ? (
<div className="tw-relative tw-h-full tw-w-full">
<Square2StackIcon
className="tw-pointer-events-none tw-absolute tw-left-6 tw-top-8 tw-z-10 tw-size-6 tw-text-iron-700 tw-opacity-50"
aria-hidden="true"
/>
Comment thread
ragnep marked this conversation as resolved.
{/* Glow effect */}
<div
className="tw-pointer-events-none tw-absolute tw-inset-0 tw-opacity-40 tw-blur-3xl tw-transition-opacity tw-duration-700 group-hover:tw-opacity-60"
Expand Down
9 changes: 8 additions & 1 deletion components/home/carousel/CarouselActiveItemDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { useEffect, useState } from "react";

interface CarouselActiveItemDetailsProps {
readonly drop: ExtendedDrop | null;
Expand All @@ -16,6 +16,11 @@ export default function CarouselActiveItemDetails({
drop,
}: CarouselActiveItemDetailsProps) {
const [isExpanded, setIsExpanded] = useState(false);
const dropId = drop?.id;

useEffect(() => {
setIsExpanded(false);
}, [dropId]);

if (!drop) {
return null;
Expand All @@ -33,13 +38,15 @@ export default function CarouselActiveItemDetails({
return (
<div className="tw-mx-auto tw-flex tw-w-full tw-max-w-2xl tw-flex-col tw-items-center tw-px-4 tw-py-4 md:tw-px-6 lg:tw-px-8">
<Link
prefetch={false}
href={`/waves?wave=${drop.wave.id}&drop=${drop.id}`}
className="tw-mb-2 tw-block tw-text-center tw-text-lg tw-font-semibold tw-text-white tw-no-underline desktop-hover:hover:tw-text-white md:tw-text-xl"
>
{title}
</Link>

<Link
prefetch={false}
href={`/${author.handle ?? author.primary_address}`}
className="tw-flex tw-items-center tw-gap-x-2 tw-no-underline"
>
Expand Down
49 changes: 34 additions & 15 deletions components/home/carousel/CarouselActiveItemVote.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { useCallback, useState } from "react";
import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { WaveDropVoteSummary } from "@/components/waves/drop/WaveDropVoteSummary";
import { VotingModal, MobileVotingModal } from "@/components/voting";
Expand All @@ -9,15 +9,33 @@ import { useDropInteractionRules } from "@/hooks/drops/useDropInteractionRules";

interface CarouselActiveItemVoteProps {
readonly drop: ExtendedDrop;
readonly onVoteOpen?: (() => void) | undefined;
readonly onVoteClose?: (() => void) | undefined;
}

export default function CarouselActiveItemVote({
drop,
onVoteOpen,
onVoteClose,
}: CarouselActiveItemVoteProps) {
const [isVotingOpen, setIsVotingOpen] = useState(false);
const [lockedDrop, setLockedDrop] = useState<ExtendedDrop | null>(null);
const isMobileScreen = useIsMobileScreen();
const { canShowVote, isVotingEnded, isWinner } =
useDropInteractionRules(drop);
const modalDrop = lockedDrop ?? drop;

const handleOpenVote = useCallback(() => {
setLockedDrop(drop);
setIsVotingOpen(true);
onVoteOpen?.();
}, [drop, onVoteOpen]);

const handleCloseVote = useCallback(() => {
setIsVotingOpen(false);
setLockedDrop(null);
onVoteClose?.();
}, [onVoteClose]);

return (
<div className="tw-mt-4 tw-flex tw-items-center tw-justify-center tw-px-4 md:tw-px-6 lg:tw-px-8">
Expand All @@ -26,22 +44,23 @@ export default function CarouselActiveItemVote({
isWinner={isWinner}
isVotingEnded={isVotingEnded}
canShowVote={canShowVote}
onVoteClick={() => setIsVotingOpen(true)}
onVoteClick={handleOpenVote}
/>

{isMobileScreen ? (
<MobileVotingModal
drop={drop}
isOpen={isVotingOpen}
onClose={() => setIsVotingOpen(false)}
/>
) : (
<VotingModal
drop={drop}
isOpen={isVotingOpen}
onClose={() => setIsVotingOpen(false)}
/>
)}
{isVotingOpen &&
(isMobileScreen ? (
<MobileVotingModal
drop={modalDrop}
isOpen={true}
onClose={handleCloseVote}
/>
) : (
<VotingModal
drop={modalDrop}
isOpen={true}
onClose={handleCloseVote}
/>
))}
</div>
);
}
2 changes: 1 addition & 1 deletion components/home/carousel/CarouselArrow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function CarouselArrow({
onClick={onClick}
disabled={disabled}
aria-label={isLeft ? "Previous" : "Next"}
className={`${placementClasses} tw-z-20 tw-flex tw-h-11 tw-w-11 tw-items-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-white/10 tw-bg-white/20 tw-text-white tw-backdrop-blur-md tw-transition-all tw-duration-300 hover:tw-border-white/20 hover:tw-bg-white/[0.15] disabled:tw-cursor-not-allowed disabled:tw-opacity-40 disabled:hover:tw-border-white/10 disabled:hover:tw-bg-white/10 ${className}`}
className={`${placementClasses} tw-z-20 tw-flex tw-h-11 tw-w-11 tw-items-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-white/10 tw-bg-white/20 tw-text-white tw-backdrop-blur-md tw-transition-all tw-duration-300 hover:tw-border-white/20 hover:tw-bg-white/[0.15] disabled:tw-cursor-not-allowed disabled:tw-opacity-45 disabled:tw-text-white/60 disabled:tw-border-white/10 disabled:tw-bg-white/15 disabled:hover:tw-border-white/15 disabled:hover:tw-bg-white/15 ${className}`}
style={style}
>
<FontAwesomeIcon
Expand Down
Loading