diff --git a/__tests__/components/waves/list/WaveItemDropped.test.tsx b/__tests__/components/waves/list/WaveItemDropped.test.tsx index 85ef298203..4c1dc8f244 100644 --- a/__tests__/components/waves/list/WaveItemDropped.test.tsx +++ b/__tests__/components/waves/list/WaveItemDropped.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import WaveItemDropped from '@/components/waves/list/WaveItemDropped'; jest.mock('next/link', () => ({ __esModule: true, default: ({ href, children }: any) => {children} })); +jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn() }) })); jest.mock('@/helpers/Helpers', () => ({ numberWithCommas: (n: number) => n.toString() })); jest.mock('@/helpers/image.helpers', () => ({ getScaledImageUri: (u: string) => `scaled-${u}`, ImageScale: { W_AUTO_H_50: 'scale' } })); diff --git a/codex/STATE.md b/codex/STATE.md index 87fcc94abf..92b36d0425 100644 --- a/codex/STATE.md +++ b/codex/STATE.md @@ -24,6 +24,7 @@ This table is the single source of truth for active and historical tickets. Keep | TKT-0018 | Clean up UrlGuardHooks export | In-Progress | P1 | openai-assistant | — | 2025-10-28 | | TKT-0019 | Make Wave card fully clickable | In-Progress | P1 | openai-assistant | — | 2025-10-27 | | TKT-0020 | Harden CustomTooltip positioning robustness | In-Progress | P1 | openai-assistant | — | 2025-10-28 | +| TKT-0021 | Restore Discover create wave modal | In-Progress | P1 | openai-assistant | — | 2025-10-29 | ## Usage Guidelines diff --git a/codex/tickets/TKT-0021.md b/codex/tickets/TKT-0021.md new file mode 100644 index 0000000000..305a578aa8 --- /dev/null +++ b/codex/tickets/TKT-0021.md @@ -0,0 +1,39 @@ +--- +created: 2025-10-29 +id: TKT-0021 +owner: openai-assistant +priority: P1 +status: In-Progress +title: Restore Discover create wave modal +--- + +## Context + +> The Discover page's `Create Wave` CTA should open the modal overlay, but a recent redesign causes the main content to swap to the inline creation form instead. Need to restore the modal behavior without regressing the existing direct message modal or in-app handling. + +## Plan + +- [ ] Reintroduce the wave modal in `Waves` when running on web while keeping app behavior intact. +- [ ] Ensure the inline view is only used where appropriate and URL query strings still toggle modal visibility. +- [ ] Verify both `Create Wave` and `Create DM` buttons behave correctly on Discover. + +## Acceptance + +- [ ] Clicking `Create Wave` on Discover opens the modal overlay and leaves the list view visible. +- [ ] Closing/success from the modal clears the `create` query parameter and returns to the list. +- [ ] `Create DM` button continues to open its modal. +- [ ] `npm run lint`, `npm run type-check`, and targeted tests succeed when run. + +## Links + +- Primary PR: _(add when available)_ +- Follow-ups: _(reference additional tickets or TODO items)_ + +## Log + +- 2025-10-29T07:06:06Z – Created ticket and triaged regression scope. +- 2025-10-29T07:08:01Z – Wired `CreateWaveModal` back into `Waves` for web, keeping inline create for app, and ran `npm run lint`. +- 2025-10-29T07:10:05Z – Confirmed `npm run type-check` and `npm run test:cov:changed` pass (pre-existing console warnings only). +- 2025-10-29T07:15:11Z – Resolved nested link warning by making the card container a focusable div with keyboard/middle-click routing and re-ran checks. +- 2025-10-29T07:48:13Z – Restored outer `` semantics with internal button replacements, updated contributor avatars to use buttons, adjusted tests, and reran lint/type-check/tests. +- 2025-10-29T08:22:29Z – Added missing `"use client"` directive to `WaveItemDropped` per hook usage guidance. diff --git a/components/waves/Waves.tsx b/components/waves/Waves.tsx index 908a385d6d..b231c368f8 100644 --- a/components/waves/Waves.tsx +++ b/components/waves/Waves.tsx @@ -8,6 +8,7 @@ import { useRouter } from "next/navigation"; import { useCallback, useMemo, type JSX } from "react"; import CreateDirectMessage from "./create-dm/CreateDirectMessage"; import CreateWave from "./create-wave/CreateWave"; +import CreateWaveModal from "./create-wave/CreateWaveModal"; import WavesList from "./list/WavesList"; import ConnectWallet from "@/components/common/ConnectWallet"; import { getWavesBaseRoute } from "@/helpers/navigation.helpers"; @@ -45,6 +46,7 @@ export default function Waves({ openDirectMessage, close, isApp, + isWaveModalOpen, } = useCreateModalState(); useSetTitle(documentTitle); @@ -173,21 +175,25 @@ export default function Waves({ ), }; - const activeView = - isApp || viewMode === WavesViewMode.CREATE - ? viewMode - : WavesViewMode.VIEW; + const activeView = isApp ? viewMode : WavesViewMode.VIEW; return (
{components[activeView]} {!isApp && connectedProfile && ( - + <> + + + )}
); diff --git a/components/waves/list/WaveItem.tsx b/components/waves/list/WaveItem.tsx index 9cac87debd..7674554c17 100644 --- a/components/waves/list/WaveItem.tsx +++ b/components/waves/list/WaveItem.tsx @@ -60,7 +60,7 @@ function CardContainer({ }: CardContainerProps) { const className = `${CARD_BASE_CLASSES} ${ isInteractive ? CARD_INTERACTIVE_CLASSES : "" - }`; + } tw-no-underline`; if (isInteractive && href) { return ( @@ -69,7 +69,6 @@ function CardContainer({ prefetch={false} className={className} aria-label={ariaLabel} - style={{ textDecoration: "none" }} onClick={onClick} onKeyDown={onKeyDown} > @@ -176,6 +175,36 @@ export default function WaveItem({ "tw-text-sm tw-font-semibold tw-text-white desktop-hover:group-hover/author:tw-text-iron-400 tw-transition tw-duration-300 tw-ease-out"; const staticAuthorNameClass = "tw-text-sm tw-font-semibold tw-text-white"; + const handleAuthorClick = useCallback( + (event: MouseEvent) => { + if (!authorHref) { + return; + } + if (event.metaKey || event.ctrlKey) { + event.preventDefault(); + event.stopPropagation(); + window.open(authorHref, "_blank", "noopener,noreferrer"); + return; + } + event.preventDefault(); + event.stopPropagation(); + router.push(authorHref); + }, + [authorHref, router] + ); + + const handleAuthorAuxClick = useCallback( + (event: MouseEvent) => { + if (!authorHref || event.button !== 1) { + return; + } + event.preventDefault(); + event.stopPropagation(); + window.open(authorHref, "_blank", "noopener,noreferrer"); + }, + [authorHref] + ); + let authorSection: ReactNode; if (!wave) { authorSection = ( @@ -188,17 +217,24 @@ export default function WaveItem({ ); } else if (authorHref) { authorSection = ( -
{authorAvatar}
{author?.handle ?? userPlaceholder} {authorLevelBadge} - + ); } else { authorSection = ( @@ -219,6 +255,11 @@ export default function WaveItem({ } const target = event.target as HTMLElement | null; if (shouldSkipNavigation(target, event.currentTarget)) { + event.preventDefault(); + event.stopPropagation(); + return; + } + if (event.button === 1 || event.metaKey || event.ctrlKey) { return; } if (!event.defaultPrevented) { @@ -277,19 +318,9 @@ export default function WaveItem({
- {waveHref ? ( - - {wave?.name ?? titlePlaceholder} - - ) : ( - - {wave?.name ?? titlePlaceholder} - - )} + + {wave?.name ?? titlePlaceholder} +
diff --git a/components/waves/list/WaveItemDropped.tsx b/components/waves/list/WaveItemDropped.tsx index 1df93a2082..6d22d6ad38 100644 --- a/components/waves/list/WaveItemDropped.tsx +++ b/components/waves/list/WaveItemDropped.tsx @@ -1,10 +1,43 @@ -import Link from "next/link"; +"use client"; + +import { MouseEvent, useCallback } from "react"; +import { useRouter } from "next/navigation"; import { ApiWave } from "@/generated/models/ApiWave"; import { numberWithCommas } from "@/helpers/Helpers"; import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) { const contributors = wave.contributors_overview ?? []; + const router = useRouter(); + + const handleContributorClick = useCallback( + (href: string) => + (event: MouseEvent) => { + if (event.metaKey || event.ctrlKey) { + event.preventDefault(); + event.stopPropagation(); + window.open(href, "_blank", "noopener,noreferrer"); + return; + } + event.preventDefault(); + event.stopPropagation(); + router.push(href); + }, + [router] + ); + + const handleContributorAuxClick = useCallback( + (href: string) => + (event: MouseEvent) => { + if (event.button !== 1) { + return; + } + event.preventDefault(); + event.stopPropagation(); + window.open(href, "_blank", "noopener,noreferrer"); + }, + [] + ); return (
@@ -45,9 +78,20 @@ export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) { return (
{contributorHref ? ( - + ) : ( avatar )} diff --git a/scripts/dev-with-fallback.cjs b/scripts/dev-with-fallback.cjs index 4400f8f9d2..dd15eb2408 100644 --- a/scripts/dev-with-fallback.cjs +++ b/scripts/dev-with-fallback.cjs @@ -46,7 +46,11 @@ async function findAvailablePort() { async function run() { try { const port = await findAvailablePort(); - const env = { ...process.env, PORT: String(port) }; + const env = { + ...process.env, + __NEXT_EXPERIMENTAL_MCP_SERVER: "true", + PORT: String(port), + }; console.log(`Starting Next.js dev server on port ${port}...`); const child = spawn(