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
27 changes: 15 additions & 12 deletions components/brain/my-stream/MyStreamWave.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import MyStreamWaveFAQ from "./MyStreamWaveFAQ";
import { useMyStream } from "@/contexts/wave/MyStreamContext";
import { createBreakpoint } from "react-use";
import { getHomeFeedRoute } from "@/helpers/navigation.helpers";
import { WaveChatScrollProvider } from "@/contexts/wave/WaveChatScrollContext";

interface MyStreamWaveProps {
readonly waveId: string;
Expand Down Expand Up @@ -95,20 +96,22 @@ const MyStreamWave: React.FC<MyStreamWaveProps> = ({ waveId }) => {
};

return (
<div
className="tailwind-scope tw-relative tw-flex tw-flex-col tw-h-full"
key={stableWaveKey}>
{/* Always render tab container (hidden on app inside MyStreamWaveTabs) */}
<MyStreamWaveTabs wave={wave} />

<WaveChatScrollProvider>
<div
className="tw-flex-grow tw-overflow-hidden tw-relative"
role="tabpanel"
id={getContentTabPanelId(activeContentTab)}
>
{components[activeContentTab]}
className="tailwind-scope tw-relative tw-flex tw-flex-col tw-h-full"
key={stableWaveKey}>
{/* Always render tab container (hidden on app inside MyStreamWaveTabs) */}
<MyStreamWaveTabs wave={wave} />

<div
className="tw-flex-grow tw-overflow-hidden tw-relative"
role="tabpanel"
id={getContentTabPanelId(activeContentTab)}
>
{components[activeContentTab]}
</div>
</div>
</div>
</WaveChatScrollProvider>
);
};

Expand Down
30 changes: 16 additions & 14 deletions components/brain/my-stream/MyStreamWaveChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const MyStreamWaveChat: React.FC<MyStreamWaveChatProps> = ({ wave }) => {
const pathname = usePathname();
const containerRef = useRef<HTMLDivElement>(null);
const [initialDrop, setInitialDrop] = useState<number | null>(null);
const [searchParamsDone, setSearchParamsDone] = useState(false);
const { isMemesWave } = useWave(wave);
const editingDropId = useSelector(selectEditingDropId);
const { isApp } = useDeviceInfo();
Expand All @@ -40,18 +39,24 @@ const MyStreamWaveChat: React.FC<MyStreamWaveChatProps> = ({ wave }) => {
// Handle URL parameters
useEffect(() => {
const dropParam = searchParams?.get("serialNo");
if (dropParam) {
setInitialDrop(parseInt(dropParam));
const params = new URLSearchParams(searchParams?.toString() || '');
params.delete("serialNo");
const href = params.toString()
? `${pathname}?${params.toString()}`
: (pathname || getHomeFeedRoute());
router.replace(href, { scroll: false });
} else {
if (!dropParam) {
setInitialDrop(null);
return;
}
setSearchParamsDone(true);

const parsed = Number.parseInt(dropParam, 10);
if (!Number.isFinite(parsed)) {
return;
}

setInitialDrop(parsed);

const params = new URLSearchParams(searchParams?.toString() || "");
params.delete("serialNo");
const href = params.toString()
? `${pathname}?${params.toString()}`
: pathname || getHomeFeedRoute();
router.replace(href, { scroll: false });
}, [searchParams, router, pathname]);

const { waveViewStyle } = useLayout();
Expand Down Expand Up @@ -99,9 +104,6 @@ const MyStreamWaveChat: React.FC<MyStreamWaveChatProps> = ({ wave }) => {
setActiveDrop(null);
};

if (!searchParamsDone) {
return null;
}
return (
<div
ref={containerRef}
Expand Down
84 changes: 70 additions & 14 deletions components/brain/my-stream/tabs/MyStreamWaveTabsDefault.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React from "react";
import React, { useState } from "react";
import { ApiWave } from "@/generated/models/ApiWave";
import MyStreamWaveDesktopTabs from "../MyStreamWaveDesktopTabs";
import { useContentTab } from "@/components/brain/ContentTabContext";
import { useSidebarState } from "../../../../hooks/useSidebarState";
import {
ChevronDoubleLeftIcon,
ArrowLeftIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";
import WavePicture from "../../../waves/WavePicture";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { createBreakpoint } from "react-use";
import WaveDropsSearchModal from "@/components/waves/drops/search/WaveDropsSearchModal";
import { MyStreamWaveTab } from "@/types/waves.types";
import { useWaveChatScrollOptional } from "@/contexts/wave/WaveChatScrollContext";

const useBreakpoint = createBreakpoint({ LG: 1024, S: 0 });
interface MyStreamWaveTabsDefaultProps {
Expand All @@ -27,6 +31,8 @@ const MyStreamWaveTabsDefault: React.FC<MyStreamWaveTabsDefaultProps> = ({
const pathname = usePathname();
const breakpoint = useBreakpoint();
const isMobile = breakpoint === "S";
const [isSearchOpen, setIsSearchOpen] = useState(false);
const waveChatScroll = useWaveChatScrollOptional();

const handleMobileBack = () => {
const params = new URLSearchParams(searchParams?.toString() || "");
Expand All @@ -37,10 +43,22 @@ const MyStreamWaveTabsDefault: React.FC<MyStreamWaveTabsDefaultProps> = ({
router.push(newUrl, { scroll: false });
};

const handleSearchSelect = (serialNo: number) => {
setActiveContentTab(MyStreamWaveTab.CHAT);
if (waveChatScroll) {
waveChatScroll.requestScrollToSerialNo({ waveId: wave.id, serialNo });
return;
}

const params = new URLSearchParams(searchParams?.toString() || "");
params.set("serialNo", String(serialNo));
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
};

return (
<div className="tw-w-full tw-flex tw-flex-col tw-bg-iron-950">
<div className="tw-flex tw-items-center tw-justify-between tw-gap-x-4 tw-px-2 sm:tw-px-4 tw-py-3 tw-border-b tw-border-solid tw-border-iron-800 tw-border-x-0 tw-border-t-0">
<div className="tw-flex tw-items-center">
<div className="tw-flex tw-items-center tw-min-w-0">
{isMobile && (
<button
onClick={handleMobileBack}
Expand All @@ -50,18 +68,50 @@ const MyStreamWaveTabsDefault: React.FC<MyStreamWaveTabsDefaultProps> = ({
<ArrowLeftIcon className="tw-w-6 tw-h-6 tw-flex-shrink-0" />
</button>
)}
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
{isMobile ? (
<button
type="button"
onClick={() => setIsSearchOpen(true)}
aria-label="Search messages in this wave"
className="tw-flex tw-items-center tw-bg-transparent tw-border-0 tw-p-0 tw-text-left tw-min-w-0"
>
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
</button>
) : (
<>
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
<button
type="button"
onClick={() => setIsSearchOpen(true)}
aria-label="Search messages in this wave"
className="tw-ml-2 tw-flex tw-items-center tw-justify-center tw-h-8 tw-w-8 tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-iron-200 hover:tw-border-iron-500 hover:tw-bg-iron-800 hover:tw-text-white tw-transition tw-duration-150"
>
<MagnifyingGlassIcon className="tw-h-4 tw-w-4 tw-flex-shrink-0" />
</button>
</>
)}
</div>
<div className="tw-relative tw-flex tw-items-center tw-self-stretch">
<button
Expand All @@ -86,6 +136,12 @@ const MyStreamWaveTabsDefault: React.FC<MyStreamWaveTabsDefaultProps> = ({
setActiveTab={setActiveContentTab}
/>
</div>
<WaveDropsSearchModal
isOpen={isSearchOpen}
onClose={() => setIsSearchOpen(false)}
wave={wave}
onSelectSerialNo={handleSearchSelect}
/>
</div>
);
};
Expand Down
82 changes: 69 additions & 13 deletions components/brain/my-stream/tabs/MyStreamWaveTabsMeme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { useSidebarState } from "../../../../hooks/useSidebarState";
import {
ChevronDoubleLeftIcon,
ArrowLeftIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";
import WavePicture from "../../../waves/WavePicture";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { createBreakpoint } from "react-use";
import WaveDropsSearchModal from "@/components/waves/drops/search/WaveDropsSearchModal";
import { MyStreamWaveTab } from "@/types/waves.types";
import { useWaveChatScrollOptional } from "@/contexts/wave/WaveChatScrollContext";

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

Expand All @@ -41,6 +45,8 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
const pathname = usePathname();
const breakpoint = useBreakpoint();
const isMobile = breakpoint === "S";
const [isSearchOpen, setIsSearchOpen] = useState(false);
const waveChatScroll = useWaveChatScrollOptional();

const {
isMemesWave,
Expand Down Expand Up @@ -104,12 +110,24 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
router.push(newUrl, { scroll: false });
};

const handleSearchSelect = (serialNo: number) => {
setActiveContentTab(MyStreamWaveTab.CHAT);
if (waveChatScroll) {
waveChatScroll.requestScrollToSerialNo({ waveId: wave.id, serialNo });
return;
}

const params = new URLSearchParams(searchParams?.toString() || "");
params.set("serialNo", String(serialNo));
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
};

return (
<>
{" "}
<div className="tw-w-full tw-flex tw-flex-col tw-bg-iron-950">
<div className="tw-flex tw-items-center tw-justify-between tw-gap-x-4 tw-px-2 sm:tw-px-4 tw-py-3 tw-overflow-x-hidden">
<div className="tw-flex tw-items-center">
<div className="tw-flex tw-items-center tw-min-w-0">
{isMobile && (
<button
onClick={handleMobileBack}
Expand All @@ -119,18 +137,50 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
<ArrowLeftIcon className="tw-w-6 tw-h-6 tw-flex-shrink-0" />
</button>
)}
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
{isMobile ? (
<button
type="button"
onClick={() => setIsSearchOpen(true)}
aria-label="Search messages in this wave"
className="tw-flex tw-items-center tw-bg-transparent tw-border-0 tw-p-0 tw-text-left tw-min-w-0"
>
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
</button>
) : (
<>
<div className="tw-size-6 lg:tw-size-9 tw-flex-shrink-0 tw-ring-1 tw-ring-offset-1 tw-ring-offset-iron-950 tw-ring-white/30 tw-rounded-full">
<WavePicture
name={wave.name}
picture={wave.picture}
contributors={wave.contributors_overview.map((c) => ({
pfp: c.contributor_pfp,
}))}
/>
</div>
<h1 className="tw-ml-3 tw-text-sm lg:tw-text-xl tw-font-semibold tw-text-white/95 tw-tracking-tight tw-mb-0 tw-truncate">
{wave.name}
</h1>
<button
type="button"
onClick={() => setIsSearchOpen(true)}
aria-label="Search messages in this wave"
className="tw-ml-2 tw-flex tw-items-center tw-justify-center tw-h-8 tw-w-8 tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-text-iron-200 hover:tw-border-iron-500 hover:tw-bg-iron-800 hover:tw-text-white tw-transition tw-duration-150"
>
<MagnifyingGlassIcon className="tw-h-4 tw-w-4 tw-flex-shrink-0" />
</button>
</>
)}
</div>
<div className="tw-flex tw-items-center tw-gap-x-2 tw-relative tw-pr-10">
<div className="tw-hidden lg:tw-block">
Expand Down Expand Up @@ -175,6 +225,12 @@ const MyStreamWaveTabsMeme: React.FC<MyStreamWaveTabsMemeProps> = ({
wave={wave}
onClose={() => setIsMemesModalOpen(false)}
/>
<WaveDropsSearchModal
isOpen={isSearchOpen}
onClose={() => setIsSearchOpen(false)}
wave={wave}
onSelectSerialNo={handleSearchSelect}
/>
</>
);
};
Expand Down
Loading