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
99 changes: 50 additions & 49 deletions components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,62 +305,63 @@ const MyStreamWaveDesktopTabs: React.FC<MyStreamWaveDesktopTabsProps> = ({

return (
<div className="tw-flex tw-w-full tw-items-center tw-gap-3 tw-px-2 tw-@container/tabs sm:tw-px-4">
<div className="tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-1 sm:tw-hidden">
<div
ref={mobileTabsScrollerRef}
className="tw-min-w-0 tw-flex-1 tw-overflow-x-auto tw-scrollbar-thin tw-scrollbar-track-iron-800 tw-scrollbar-thumb-iron-500 hover:tw-scrollbar-thumb-iron-300"
>
<div className="tw-inline-flex tw-items-center tw-gap-1">
<TabToggle
options={mobileOptions}
activeKey={activeKey}
onSelect={(key) => {
if (key.startsWith("curation:")) {
onSelectCuration(key.replace("curation:", ""));
return;
}

onSelectCuration(null);
setActiveTab(key as MyStreamWaveTab);
}}
/>
{mobileOverflowItems.length > 0 && (
<CompactMenu
triggerClassName="tw-inline-flex tw-h-9 tw-w-9 tw-flex-shrink-0 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-iron-200 tw-transition hover:tw-border-iron-500 hover:tw-bg-iron-800 hover:tw-text-white"
trigger={<EllipsisVerticalIcon className="tw-h-5 tw-w-5" />}
aria-label="More curations"
items={mobileOverflowItems}
menuWidthClassName="tw-w-52"
/>
)}
</div>
</div>
</div>
<div className="tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-1 sm:tw-hidden">
<div
ref={desktopTabsScrollerRef}
className="tw-hidden tw-min-w-0 tw-flex-1 tw-overflow-x-auto tw-scrollbar-thin tw-scrollbar-track-iron-800 tw-scrollbar-thumb-iron-500 hover:tw-scrollbar-thumb-iron-300 sm:tw-block"
ref={mobileTabsScrollerRef}
className="tw-min-w-0 tw-flex-1 tw-overflow-x-auto tw-scrollbar-thin tw-scrollbar-track-iron-800 tw-scrollbar-thumb-iron-500 hover:tw-scrollbar-thumb-iron-300"
>
<TabToggle
options={options}
activeKey={activeKey}
onSelect={(key) => {
if (key.startsWith("curation:")) {
onSelectCuration(key.replace("curation:", ""));
return;
}

onSelectCuration(null);
setActiveTab(key as MyStreamWaveTab);
}}
/>
<div className="tw-inline-flex tw-items-center tw-gap-1">
<TabToggle
options={mobileOptions}
activeKey={activeKey}
onSelect={(key) => {
if (key.startsWith("curation:")) {
onSelectCuration(key.replace("curation:", ""));
return;
}

onSelectCuration(null);
setActiveTab(key as MyStreamWaveTab);
}}
/>
{mobileOverflowItems.length > 0 && (
<CompactMenu
triggerClassName="tw-inline-flex tw-h-9 tw-w-9 tw-flex-shrink-0 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-iron-200 tw-transition hover:tw-border-iron-500 hover:tw-bg-iron-800 hover:tw-text-white"
trigger={<EllipsisVerticalIcon className="tw-h-5 tw-w-5" />}
aria-label="More curations"
items={mobileOverflowItems}
menuWidthClassName="tw-w-52"
/>
)}
</div>
</div>
{showCreateCurationAction && (
</div>
<div
ref={desktopTabsScrollerRef}
className="tw-hidden tw-min-w-0 tw-flex-1 tw-overflow-x-auto tw-scrollbar-thin tw-scrollbar-track-iron-800 tw-scrollbar-thumb-iron-500 hover:tw-scrollbar-thumb-iron-300 sm:tw-block"
>
<TabToggle
options={options}
activeKey={activeKey}
onSelect={(key) => {
if (key.startsWith("curation:")) {
onSelectCuration(key.replace("curation:", ""));
return;
}

onSelectCuration(null);
setActiveTab(key as MyStreamWaveTab);
}}
/>
</div>
{showCreateCurationAction && (
<div className="sm:tw-ml-auto">
<MyStreamWaveCreateCurationAction
wave={wave}
onCreated={onSelectCuration}
className="sm:tw-ml-auto"
/>
)}
</div>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { PlusIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import type { ApiWave } from "@/generated/models/ApiWave";
import { useWaveCurations } from "@/hooks/waves/useWaveCurations";
import MyStreamActionTooltip from "../MyStreamActionTooltip";
Expand All @@ -11,13 +10,11 @@ import { useState } from "react";
interface MyStreamWaveCreateCurationActionProps {
readonly wave: ApiWave;
readonly onCreated: (curationId: string) => void;
readonly className?: string | undefined;
}

export default function MyStreamWaveCreateCurationAction({
wave,
onCreated,
className,
}: MyStreamWaveCreateCurationActionProps) {
const { data: curations = [] } = useWaveCurations({
waveId: wave.id,
Expand All @@ -32,45 +29,41 @@ export default function MyStreamWaveCreateCurationAction({

const createCurationTooltipId = `my-stream-create-curation-${wave.id}`;
const showCreateFirstCurationCallout = curations.length === 0;
const createButtonTooltipProps = showCreateFirstCurationCallout
? {}
: {
"data-tooltip-id": createCurationTooltipId,
"data-tooltip-content": "Create curation",
};
const handleOpenCreateCuration = () => setIsCreateCurationOpen(true);
const baseButtonClassName =
"tw-inline-flex tw-h-9 tw-w-9 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-transition desktop-hover:hover:tw-border-iron-500 desktop-hover:hover:tw-bg-iron-800 desktop-hover:hover:tw-text-white";

return (
<>
<div
className={clsx(
"tw-flex tw-flex-shrink-0 tw-items-center tw-gap-2",
className
)}
>
<button
type="button"
onClick={() => setIsCreateCurationOpen(true)}
{...createButtonTooltipProps}
className={
showCreateFirstCurationCallout
? "tw-inline-flex tw-h-9 tw-w-9 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-xs tw-font-semibold tw-text-iron-100 tw-transition desktop-hover:hover:tw-border-iron-500 desktop-hover:hover:tw-bg-iron-800 desktop-hover:hover:tw-text-white md:tw-h-auto md:tw-w-auto md:tw-gap-2 md:tw-rounded-lg md:tw-px-3.5 md:tw-py-2"
: "tw-inline-flex tw-h-9 tw-w-9 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-iron-200 tw-transition desktop-hover:hover:tw-border-iron-500 desktop-hover:hover:tw-bg-iron-800 desktop-hover:hover:tw-text-white"
}
aria-label="Create curation"
>
<PlusIcon
className={`tw-h-4 tw-w-4 tw-flex-shrink-0 ${
showCreateFirstCurationCallout ? "md:-tw-ml-1" : ""
}`}
/>
{showCreateFirstCurationCallout && (
<div className="tw-flex tw-flex-shrink-0 tw-items-center tw-gap-2 tw-pr-2 sm:tw-pr-4">
{showCreateFirstCurationCallout ? (
<button
type="button"
onClick={handleOpenCreateCuration}
className={`${baseButtonClassName} tw-text-xs tw-font-semibold tw-text-iron-100 md:tw-h-auto md:tw-w-auto md:tw-gap-2 md:tw-rounded-lg md:tw-px-3.5 md:tw-py-2`}
aria-label="Create curation"
>
<PlusIcon className="tw-h-4 tw-w-4 tw-flex-shrink-0 md:-tw-ml-1" />
<span className="tw-hidden tw-whitespace-nowrap md:tw-inline">
Create first curation
</span>
)}
</button>
</button>
) : (
<button
type="button"
onClick={handleOpenCreateCuration}
data-tooltip-id={createCurationTooltipId}
data-tooltip-content="Create curation"
className={`${baseButtonClassName} tw-text-iron-200`}
aria-label="Create curation"
>
<PlusIcon className="tw-h-4 tw-w-4 tw-flex-shrink-0" />
</button>
)}
</div>
<MyStreamActionTooltip id={createCurationTooltipId} />
{!showCreateFirstCurationCallout && (
<MyStreamActionTooltip id={createCurationTooltipId} />
)}

{isCreateCurationOpen && (
<MyStreamWaveCurationCreateDialog
Expand Down
67 changes: 1 addition & 66 deletions components/brain/my-stream/tabs/MyStreamWaveTabsMeme.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"use client";

import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import type { ApiWave } from "@/generated/models/ApiWave";
import MyStreamWaveDesktopTabs from "../MyStreamWaveDesktopTabs";
import { useContentTab } from "@/components/brain/ContentTabContext";
import MemesArtSubmissionModal from "@/components/waves/memes/MemesArtSubmissionModal";
import MyStreamWaveTabsMemeSubmit from "./MyStreamWaveTabsMemeSubmit";
import { useWave } from "../../../../hooks/useWave";
import { useDecisionPoints } from "../../../../hooks/waves/useDecisionPoints";
import { Time } from "../../../../helpers/time";
import type { TimeLeft } from "../../../../helpers/waves/time.utils";
import { calculateTimeLeft } from "../../../../helpers/waves/time.utils";
import { CompactTimeCountdown } from "../../../waves/leaderboard/time/CompactTimeCountdown";
import { useSidebarState } from "../../../../hooks/useSidebarState";
import {
ChevronDoubleLeftIcon,
Expand All @@ -37,13 +31,6 @@ import MyStreamWaveCreateCurationAction from "./MyStreamWaveCreateCurationAction

const useBreakpoint = createBreakpoint({ LG: 1024, MD: 768, S: 0 });

const EMPTY_TIME_LEFT: TimeLeft = {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
};

interface MyStreamWaveTabsMemeProps {
readonly wave: ApiWave;
readonly activeCurationId: string | null;
Expand Down Expand Up @@ -104,52 +91,6 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
return <LinkIcon className="tw-h-4 tw-w-4 tw-flex-shrink-0" />;
};

const {
isMemesWave,
isRankWave,
pauses: { filterDecisionsDuringPauses },
} = useWave(wave);

const { allDecisions } = useDecisionPoints(wave);

const filteredDecisions = React.useMemo(() => {
const decisionsAsApiFormat: { decision_time: number }[] = allDecisions.map(
(decision) => ({ decision_time: decision.timestamp })
);
const filtered = filterDecisionsDuringPauses(decisionsAsApiFormat);
return allDecisions.filter((decision) =>
filtered.some((f) => f.decision_time === decision.timestamp)
);
}, [allDecisions, filterDecisionsDuringPauses]);

const nextDecisionTime =
filteredDecisions.find(
(decision) => decision.timestamp > Time.currentMillis()
)?.timestamp ?? null;

const [timeLeft, setTimeLeft] = useState<TimeLeft>(EMPTY_TIME_LEFT);

useEffect(() => {
if (typeof nextDecisionTime !== "number") return;

const intervalId = setInterval(() => {
const newTimeLeft = calculateTimeLeft(nextDecisionTime);
setTimeLeft(newTimeLeft);
if (
newTimeLeft.days === 0 &&
newTimeLeft.hours === 0 &&
newTimeLeft.minutes === 0 &&
newTimeLeft.seconds === 0
) {
clearInterval(intervalId);
}
}, 1000);
return () => clearInterval(intervalId);
}, [nextDecisionTime]);

const displayedTimeLeft =
typeof nextDecisionTime === "number" ? timeLeft : EMPTY_TIME_LEFT;

const handleMemesSubmit = () => {
setIsMemesModalOpen(true);
};
Expand Down Expand Up @@ -296,12 +237,6 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
onCreated={onSelectCuration}
/>
)}
{(isMemesWave || isRankWave) &&
typeof nextDecisionTime === "number" && (
<div className="tw-hidden md:tw-flex md:tw-flex-shrink-0 md:tw-pr-4">
<CompactTimeCountdown timeLeft={displayedTimeLeft} />
</div>
)}
</div>
</div>
<MemesArtSubmissionModal
Expand Down
31 changes: 31 additions & 0 deletions components/waves/leaderboard/WaveLeaderboardTime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import React, {
useState,
} from "react";
import type { ApiWave } from "@/generated/models/ApiWave";
import type { TimeLeft } from "@/helpers/waves/time.utils";
import { calculateTimeLeft } from "@/helpers/waves/time.utils";
import { useDecisionPoints } from "@/hooks/waves/useDecisionPoints";
import { AnimatePresence } from "framer-motion";
import { TimelineToggleHeader } from "./time/TimelineToggleHeader";
Expand All @@ -22,6 +24,12 @@ interface WaveLeaderboardTimeProps {
}

const AUTO_EXPAND_LIMIT = 5;
const EMPTY_TIME_LEFT: TimeLeft = {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
};

export const WaveLeaderboardTime: React.FC<WaveLeaderboardTimeProps> = ({
wave,
Expand All @@ -46,6 +54,7 @@ export const WaveLeaderboardTime: React.FC<WaveLeaderboardTimeProps> = ({

const [isDecisionDetailsOpen, setIsDecisionDetailsOpen] =
useState<boolean>(false);
const [timeLeft, setTimeLeft] = useState<TimeLeft>(EMPTY_TIME_LEFT);
const autoExpandFutureAttemptsRef = useRef(0);
const [timelineFocus, setTimelineFocus] = useState<"start" | "end" | null>(
null
Expand Down Expand Up @@ -75,6 +84,24 @@ export const WaveLeaderboardTime: React.FC<WaveLeaderboardTimeProps> = ({
(decision) => decision.timestamp > Time.currentMillis()
)?.timestamp ?? null;

useEffect(() => {
if (typeof nextDecisionTime !== "number") {
setTimeLeft(EMPTY_TIME_LEFT);
return;
}

const updateTimeLeft = () => {
setTimeLeft(calculateTimeLeft(nextDecisionTime));
};

updateTimeLeft();

const intervalId = globalThis.setInterval(updateTimeLeft, 1000);
return () => {
clearInterval(intervalId);
};
}, [nextDecisionTime]);

useEffect(() => {
if (nextDecisionTime !== null) {
autoExpandFutureAttemptsRef.current = 0;
Expand Down Expand Up @@ -117,6 +144,9 @@ export const WaveLeaderboardTime: React.FC<WaveLeaderboardTimeProps> = ({
};
}, [nextDecisionTime, hasMoreFuture, loadMoreFuture]);

const displayedTimeLeft =
typeof nextDecisionTime === "number" ? timeLeft : EMPTY_TIME_LEFT;

const handleLoadMorePast = () => {
if (hasMorePast) {
setTimelineFocus("start");
Expand Down Expand Up @@ -154,6 +184,7 @@ export const WaveLeaderboardTime: React.FC<WaveLeaderboardTimeProps> = ({
isOpen={isDecisionDetailsOpen}
setIsOpen={handleDecisionDetailsOpenChange}
nextDecisionTime={nextDecisionTime}
timeLeft={displayedTimeLeft}
isPaused={Boolean(currentPause)}
currentPause={currentPause}
/>
Expand Down
Loading
Loading