From e6cb1930eebe4d08a301709439614505bb72455b Mon Sep 17 00:00:00 2001 From: "ildar.timerbaev" Date: Wed, 20 Nov 2024 22:51:32 +0300 Subject: [PATCH] Added waves details page --- src/api/queries/get-discussions-query.ts | 18 ++---- src/api/queries/index.ts | 1 + .../deck-thread-item-viewer.tsx | 58 ++----------------- .../[author]/[permlink]/_components/index.ts | 2 + .../_components/wave-view-details.tsx | 46 +++++++++++++++ .../_components/wave-view-discussion.tsx | 24 ++++++++ src/app/waves/[author]/[permlink]/page.tsx | 32 +++++++--- .../_components/waves-host-selection.tsx | 2 +- src/app/waves/_components/waves-list-item.tsx | 27 ++++++++- src/app/waves/layout.tsx | 22 ++++--- src/features/shared/navbar/navbar-desktop.tsx | 6 +- .../waves/components/wave-actions.tsx | 2 +- .../wave-form/api/use-threads-api.ts | 12 ++-- src/features/waves/hooks/index.ts | 1 + .../waves/hooks/use-wave-discussions-list.ts | 56 ++++++++++++++++++ src/features/waves/index.ts | 1 + tailwind.config.ts | 2 +- 17 files changed, 214 insertions(+), 98 deletions(-) create mode 100644 src/app/waves/[author]/[permlink]/_components/index.ts create mode 100644 src/app/waves/[author]/[permlink]/_components/wave-view-details.tsx create mode 100644 src/app/waves/[author]/[permlink]/_components/wave-view-discussion.tsx create mode 100644 src/features/waves/hooks/index.ts create mode 100644 src/features/waves/hooks/use-wave-discussions-list.ts diff --git a/src/api/queries/get-discussions-query.ts b/src/api/queries/get-discussions-query.ts index 4c5b6ddec..95fc42036 100644 --- a/src/api/queries/get-discussions-query.ts +++ b/src/api/queries/get-discussions-query.ts @@ -3,7 +3,6 @@ import { Entry } from "@/entities"; import { bridgeApiCall } from "@/api/bridge"; import { parseAsset } from "@/utils"; import { SortOrder } from "@/enums"; -import { IdentifiableEntry } from "@/app/decks/_components/columns/deck-threads-manager"; import { QueryClient } from "@tanstack/react-query"; export function sortDiscussions(entry: Entry, discussion: Entry[], order: SortOrder) { @@ -102,13 +101,10 @@ export const getDiscussionsMapQuery = (entry: Entry | undefined, enabled: boolea EcencyQueriesManager.generateClientServerQuery({ queryKey: [QueryIdentifiers.FETCH_DISCUSSIONS_MAP, entry?.author, entry?.permlink], queryFn: async () => { - const response = await bridgeApiCall | null>( - "get_discussion", - { - author: entry!!.author, - permlink: entry!!.permlink - } - ); + const response = await bridgeApiCall | null>("get_discussion", { + author: entry!!.author, + permlink: entry!!.permlink + }); if (response) { return response; } @@ -118,11 +114,7 @@ export const getDiscussionsMapQuery = (entry: Entry | undefined, enabled: boolea refetchOnMount: true }); -export function addReplyToDiscussionsList( - entry: IdentifiableEntry, - reply: Entry, - queryClient: QueryClient -) { +export function addReplyToDiscussionsList(entry: Entry, reply: Entry, queryClient: QueryClient) { queryClient.setQueryData>( [QueryIdentifiers.FETCH_DISCUSSIONS_MAP, entry?.author, entry?.permlink], (data) => { diff --git a/src/api/queries/index.ts b/src/api/queries/index.ts index 6fc554539..8a54927c0 100644 --- a/src/api/queries/index.ts +++ b/src/api/queries/index.ts @@ -68,3 +68,4 @@ export * from "./get-chain-properties-query"; export * from "./get-gifs-query"; export * from "./spk"; export * from "./waves"; +export * from "./get-discussions-query"; diff --git a/src/app/decks/_components/columns/content-viewer/deck-thread-item-viewer.tsx b/src/app/decks/_components/columns/content-viewer/deck-thread-item-viewer.tsx index 4756ea352..0214488c0 100644 --- a/src/app/decks/_components/columns/content-viewer/deck-thread-item-viewer.tsx +++ b/src/app/decks/_components/columns/content-viewer/deck-thread-item-viewer.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import React, { useRef } from "react"; import { DeckThreadItemSkeleton, ThreadItem } from "../deck-items"; import { IdentifiableEntry } from "../deck-threads-manager"; import { DeckThreadsForm } from "../../deck-threads-form"; @@ -8,13 +8,11 @@ import { Button } from "@ui/button"; import { EcencyEntriesCacheManagement } from "@/core/caches"; import i18next from "i18next"; import { arrowLeftSvg } from "@ui/svg"; -import { - addReplyToDiscussionsList, - getDiscussionsMapQuery -} from "@/api/queries/get-discussions-query"; +import { addReplyToDiscussionsList } from "@/api/queries/get-discussions-query"; import { useMounted } from "@/utils/use-mounted"; import { useQueryClient } from "@tanstack/react-query"; import { WaveEntry } from "@/entities"; +import { useWaveDiscussionsList } from "@/features/waves"; interface Props { entry: WaveEntry; @@ -33,59 +31,13 @@ export const DeckThreadItemViewer = ({ const queryClient = useQueryClient(); const { data: entry } = - EcencyEntriesCacheManagement.getEntryQuery(initialEntry).useClientQuery(); + EcencyEntriesCacheManagement.getEntryQuery(initialEntry).useClientQuery(); const isMounted = useMounted(); - const { data: discussions } = getDiscussionsMapQuery(entry).useClientQuery(); const { addReply } = EcencyEntriesCacheManagement.useAddReply(entry); const { updateRepliesCount } = EcencyEntriesCacheManagement.useUpdateRepliesCount(entry); - const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry(); - const build = useCallback( - (dataset: Record) => { - const result: IdentifiableEntry[] = []; - const values = [...Object.values(dataset).filter((v) => v.permlink !== entry?.permlink)]; - Object.entries(dataset) - .filter(([_, v]) => v.permlink !== entry?.permlink) - .forEach(([key, value]) => { - const parent = values.find((v) => v.replies.includes(key)); - if (parent) { - const existingTempIndex = result.findIndex( - (v) => v.author === parent.author && v.permlink === parent.permlink - ); - if (existingTempIndex > -1) { - result[existingTempIndex].replies.push(value); - result[existingTempIndex].replies = result[existingTempIndex].replies.filter( - (r) => r !== key - ); - } else { - parent.replies.push(value); - parent.replies = parent.replies.filter((r) => r !== key); - result.push(parent); - } - } else if ( - result.every((r) => r.author !== value.author && r.permlink !== value.permlink) - ) { - result.push(value); - } - }); - return result; - }, - [entry?.permlink] - ); - - const data = useMemo(() => { - const tempResponse = { ...discussions }; - Object.values(tempResponse).forEach((i) => { - i.host = entry?.host ?? ""; - }); - - return build(tempResponse); - }, [build, discussions, entry?.host]); - - useEffect(() => { - updateEntryQueryData(Array.from(Object.values(discussions ?? {}))); - }, [discussions]); + const data = useWaveDiscussionsList(entry!); return (
(null); + const renderBody = useRenderWaveBody(renderAreaRef, entry, {}); + + useMount(() => renderBody()); + const status = "default"; + + return ( + + +
+ {}} + hasParent={false} + pure={false} + onEdit={() => {}} + /> +
+ + + ); +} diff --git a/src/app/waves/[author]/[permlink]/_components/wave-view-discussion.tsx b/src/app/waves/[author]/[permlink]/_components/wave-view-discussion.tsx new file mode 100644 index 000000000..9b552404a --- /dev/null +++ b/src/app/waves/[author]/[permlink]/_components/wave-view-discussion.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { WaveEntry } from "@/entities"; +import { WavesListItem } from "@/app/waves/_components"; +import { useWaveDiscussionsList } from "@/features/waves"; + +interface Props { + entry: WaveEntry; +} + +export function WaveViewDiscussion({ entry }: Props) { + const data = useWaveDiscussionsList(entry); + + return ( +
+
Replies
+
+ {data?.map((item, i) => ( + + ))} +
+
+ ); +} diff --git a/src/app/waves/[author]/[permlink]/page.tsx b/src/app/waves/[author]/[permlink]/page.tsx index 5b6e170a7..94b5738f0 100644 --- a/src/app/waves/[author]/[permlink]/page.tsx +++ b/src/app/waves/[author]/[permlink]/page.tsx @@ -1,21 +1,35 @@ -"use client"; - -import { WavesListItem } from "@/app/waves/_components"; import { EcencyEntriesCacheManagement } from "@/core/caches"; +import { notFound } from "next/navigation"; +import { WaveViewDetails, WaveViewDiscussion } from "@/app/waves/[author]/[permlink]/_components"; import { WaveEntry } from "@/entities"; +import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; +import { getQueryClient } from "@/core/react-query"; interface Props { - params: { + params: Promise<{ author: string; permlink: string; - }; + }>; } -export default function WaveViewPage({ params: { author, permlink } }: Props) { - const { data } = EcencyEntriesCacheManagement.getEntryQueryByPath( +export default async function WaveViewPage({ params }: Props) { + const { author, permlink } = await params; + + const data = (await EcencyEntriesCacheManagement.getEntryQueryByPath( author, permlink - ).useClientQuery(); + ).prefetch()) as WaveEntry; + + if (!data) { + return notFound(); + } - return
{data && }
; + return ( + +
+ + +
+
+ ); } diff --git a/src/app/waves/_components/waves-host-selection.tsx b/src/app/waves/_components/waves-host-selection.tsx index 5fe0caf29..c71ff60bf 100644 --- a/src/app/waves/_components/waves-host-selection.tsx +++ b/src/app/waves/_components/waves-host-selection.tsx @@ -14,7 +14,7 @@ export function WavesHostSelection({ host, setHost }: Props) { return (
-
+
{availableHosts.map((item, i) => ( (null); + const router = useRouter(); + const { data: entry } = EcencyEntriesCacheManagement.getEntryQuery(item).useClientQuery(); @@ -25,12 +28,31 @@ export function WavesListItem({ item, i }: Props) { useMount(() => renderBody()); const status = "default"; + const onClick = useCallback( + (e: React.MouseEvent) => { + const path = `/waves/${item.author}/${item.permlink}`; + + switch (e.button) { + case 0: + router.push(path); + break; + case 1: + window.open(path, "_blank"); + break; + default: + null; + } + }, + [item.author, item.permlink, router] + ); + return (
{}} - commentsSlot={<>} hasParent={false} pure={false} onEdit={() => {}} diff --git a/src/app/waves/layout.tsx b/src/app/waves/layout.tsx index 0e6fe62f9..62db76dd1 100644 --- a/src/app/waves/layout.tsx +++ b/src/app/waves/layout.tsx @@ -4,6 +4,7 @@ import { Feedback, Navbar, ScrollToTop } from "@/features/shared"; import { PropsWithChildren, ReactNode, useCallback, useState } from "react"; import { classNameObject } from "@ui/util"; import { useMount, useUnmount } from "react-use"; +import { usePathname } from "next/navigation"; interface Props { view: ReactNode; @@ -12,7 +13,8 @@ interface Props { export default function WavesLayout(props: PropsWithChildren) { const [scroll, setScroll] = useState(0); - const handleScroll = useCallback((event: Event) => setScroll(window.scrollY), []); + const pathname = usePathname(); + const handleScroll = useCallback(() => setScroll(window.scrollY), []); useMount(() => window.addEventListener("scroll", handleScroll)); useUnmount(() => window.removeEventListener("scroll", handleScroll)); @@ -20,17 +22,23 @@ export default function WavesLayout(props: PropsWithChildren) { return (
-
-
-
{props.children}
-
+
+
+
{props.children}
+
); diff --git a/src/features/shared/navbar/navbar-desktop.tsx b/src/features/shared/navbar/navbar-desktop.tsx index 88da0c9b2..fe51cae47 100644 --- a/src/features/shared/navbar/navbar-desktop.tsx +++ b/src/features/shared/navbar/navbar-desktop.tsx @@ -47,10 +47,10 @@ export function NavbarDesktop({ >
diff --git a/src/features/waves/components/wave-actions.tsx b/src/features/waves/components/wave-actions.tsx index 0bc4a69e2..0d7d982da 100644 --- a/src/features/waves/components/wave-actions.tsx +++ b/src/features/waves/components/wave-actions.tsx @@ -11,7 +11,7 @@ interface Props { status: string; entry: WaveEntry; onEntryView: () => void; - commentsSlot: ReactNode; + commentsSlot?: ReactNode; hasParent: boolean; pure: boolean; onEdit: (entry: WaveEntry) => void; diff --git a/src/features/waves/components/wave-form/api/use-threads-api.ts b/src/features/waves/components/wave-form/api/use-threads-api.ts index a51ca4db3..eca2277d3 100644 --- a/src/features/waves/components/wave-form/api/use-threads-api.ts +++ b/src/features/waves/components/wave-form/api/use-threads-api.ts @@ -7,10 +7,12 @@ import { createReplyPermlink, tempEntry } from "@/utils"; import { EntryMetadataManagement } from "@/features/entry-management"; import { comment } from "@/api/operations"; import { EcencyEntriesCacheManagement } from "@/core/caches"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { validatePostCreating } from "@/api/hive"; +import { addReplyToDiscussionsList } from "@/api/queries"; export function useThreadsApi() { + const queryClient = useQueryClient(); const activeUser = useGlobalStore((s) => s.activeUser); const { activePoll } = useContext(PollsContext); @@ -59,15 +61,11 @@ export function useThreadsApi() { post_id: v4() }); - // add new reply to store + // add new reply to cache addReply(nReply, entry); + addReplyToDiscussionsList(entry, nReply, queryClient); if (entry.children === 0) { - // Activate discussion [...sections] with first comment. - const nEntry: Entry = { - ...entry, - children: 1 - }; updateRepliesCount(1, entry); } diff --git a/src/features/waves/hooks/index.ts b/src/features/waves/hooks/index.ts new file mode 100644 index 000000000..b5c89f4b0 --- /dev/null +++ b/src/features/waves/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-wave-discussions-list"; diff --git a/src/features/waves/hooks/use-wave-discussions-list.ts b/src/features/waves/hooks/use-wave-discussions-list.ts new file mode 100644 index 000000000..621f3877b --- /dev/null +++ b/src/features/waves/hooks/use-wave-discussions-list.ts @@ -0,0 +1,56 @@ +import { getDiscussionsMapQuery } from "@/api/queries"; +import { useCallback, useEffect, useMemo } from "react"; +import { WaveEntry } from "@/entities"; +import { EcencyEntriesCacheManagement } from "@/core/caches"; + +export function useWaveDiscussionsList(entry: WaveEntry) { + const { data: discussions } = getDiscussionsMapQuery(entry).useClientQuery(); + const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry(); + + const build = useCallback( + (dataset: Record) => { + const result: WaveEntry[] = []; + const values = [...Object.values(dataset).filter((v) => v.permlink !== entry?.permlink)]; + Object.entries(dataset) + .filter(([_, v]) => v.permlink !== entry?.permlink) + .forEach(([key, value]) => { + const parent = values.find((v) => v.replies.includes(key)); + if (parent) { + const existingTempIndex = result.findIndex( + (v) => v.author === parent.author && v.permlink === parent.permlink + ); + if (existingTempIndex > -1) { + result[existingTempIndex].replies.push(value); + result[existingTempIndex].replies = result[existingTempIndex].replies.filter( + (r) => r !== key + ); + } else { + parent.replies.push(value); + parent.replies = parent.replies.filter((r) => r !== key); + result.push(parent); + } + } else if ( + result.every((r) => r.author !== value.author && r.permlink !== value.permlink) + ) { + result.push(value); + } + }); + console.log(result, values); + return result; + }, + [entry?.permlink] + ); + + useEffect(() => { + updateEntryQueryData(Array.from(Object.values(discussions ?? {}))); + }, [discussions, updateEntryQueryData]); + + return useMemo(() => { + const tempResponse = { ...discussions } as Record; + Object.values(tempResponse).forEach((i) => { + i.host = entry?.host ?? ""; + }); + + return build(tempResponse); + }, [build, discussions, entry?.host]); +} diff --git a/src/features/waves/index.ts b/src/features/waves/index.ts index 6cda7d408..74b777467 100644 --- a/src/features/waves/index.ts +++ b/src/features/waves/index.ts @@ -2,3 +2,4 @@ export * from "./functions"; export * from "./components"; export * from "./cache"; export * from "./consts"; +export * from "./hooks"; diff --git a/tailwind.config.ts b/tailwind.config.ts index 1f38b2daa..ef88a2d64 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -220,7 +220,7 @@ const config: Config = { "dark-sky-030": "#bdd4f7", "dark-sky-040": "#ebf2fc", "dark-sky-075": "rgba(53, 124, 230, 0.75)", - "duck-egg": "#eaf2fc", + "duck-egg": "#eff5fd", "duck-egg-200": "#91bbef", faded: "#e8f0fb", metallic: "#526d91",