Skip to content

Commit

Permalink
Merge pull request #43 from ecency/bugfix/user-issues-2
Browse files Browse the repository at this point in the history
bugfix/user-issues-2
  • Loading branch information
feruzm authored Oct 2, 2024
2 parents ceeff1c + e49f562 commit 1468913
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 113 deletions.
2 changes: 1 addition & 1 deletion public/sw.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/sw.js.map

Large diffs are not rendered by default.

21 changes: 15 additions & 6 deletions src/api/queries/get-proposal-votes-query.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { EcencyQueriesManager, QueryIdentifiers } from "@/core/react-query";
import { client } from "@/api/hive";
import { client, getAccounts } from "@/api/hive";
import { ProposalVote } from "@/entities";

export const getProposalVotesQuery = (proposalId: number, voter: string, limit: number) =>
EcencyQueriesManager.generateClientServerQuery({
EcencyQueriesManager.generateClientServerInfiniteQuery({
queryKey: [QueryIdentifiers.PROPOSAL_VOTES, proposalId, voter, limit],
queryFn: async () => {
queryFn: async ({ pageParam }) => {
const response = (await client.call("condenser_api", "list_proposal_votes", [
[proposalId, voter],
[proposalId, pageParam ?? voter],
limit,
"by_proposal_voter"
])) as ProposalVote[];
return response
const list = response
.filter((x: ProposalVote) => x.proposal.proposal_id === proposalId)
.map((x: ProposalVote) => ({ id: x.id, voter: x.voter }));
}
const accounts = await getAccounts(list.map((l) => l.voter));
return list.map((i) => ({
...i,
voterAccount: accounts.find((a) => i.voter === a.name)!
}));
},
initialData: { pages: [], pageParams: [] },
initialPageParam: "",
refetchOnMount: true,
getNextPageParam: (lastPage) => lastPage?.[lastPage.length - 1]?.voter ?? ""
});
56 changes: 34 additions & 22 deletions src/app/proposals/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client";
import { catchPostImage, renderPostBody } from "@ecency/render-helper";
import React from "react";
import { Feedback, LinearProgress, Navbar, ScrollToTop, Theme } from "@/features/shared";
import { Feedback, Navbar, ScrollToTop, Theme } from "@/features/shared";
import i18next from "i18next";
import Link from "next/link";
import { closeSvg } from "@ui/svg";
import { ProposalListItem } from "../_components";
import { parseDate } from "@/utils";
import Head from "next/head";
Expand All @@ -13,25 +11,40 @@ import { getProposalQuery } from "@/api/queries";
import { EcencyEntriesCacheManagement } from "@/core/caches";
import { useGlobalStore } from "@/core/global-store";
import "../_page.scss";

export const dynamic = "force-dynamic";
import { Metadata, ResolvingMetadata } from "next";
import { PagesMetadataGenerator } from "@/features/metadata";
import { Button } from "@ui/button";
import { UilArrowLeft } from "@tooni/iconscout-unicons-react";

export interface Props {
params: {
id: string;
};
}

export default function ProposalDetailsPage({ params: { id } }: Props) {
export async function generateMetadata(
{ params: { id } }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const proposal = await getProposalQuery(+id).prefetch();
const basic = await PagesMetadataGenerator.getForPage("proposals");
return {
...basic,
title: `${basic.title} | ${proposal?.subject}`,
description: proposal?.creator ?? basic.description
};
}

export default async function ProposalDetailsPage({ params: { id } }: Props) {
const canUseWebp = useGlobalStore((s) => s.canUseWebp);

const { data: proposal, isLoading, isSuccess, isError } = getProposalQuery(+id).useClientQuery();
const { data: entry } = EcencyEntriesCacheManagement.getEntryQueryByPath(
const proposal = await getProposalQuery(+id).prefetch();
const entry = await EcencyEntriesCacheManagement.getEntryQueryByPath(
proposal?.creator,
proposal?.permlink
).useClientQuery();
).prefetch();

if (!proposal && (isSuccess || isError)) {
if (!proposal || !entry) {
return notFound();
}

Expand Down Expand Up @@ -66,18 +79,17 @@ export default function ProposalDetailsPage({ params: { id } }: Props) {
<Theme />
<Feedback />
<Navbar />
{isLoading && <LinearProgress />}
<div className="app-content proposals-page proposals-detail-page">
<div className="page-header mt-5">
<h1 className="header-title">{i18next.t("proposals.page-title")}</h1>
<p className="see-all">
<Link href="/proposals">{i18next.t("proposals.see-all")}</Link>
</p>
</div>
<div className="proposal-list">
<Link href="/proposals" className="btn-dismiss">
{closeSvg}
</Link>
<div className="app-content proposals-page pt-16">
<Link className="block my-4 lg:my-6 xl:my-8" href="/proposals">
<Button
icon={<UilArrowLeft className="w-4 h-4" />}
iconPlacement="left"
appearance="gray-link"
>
{i18next.t("proposals.page-title")}
</Button>
</Link>
<div className="proposal-list relative">
{proposal && <ProposalListItem proposal={proposal} />}
</div>
<div className="the-entry">
Expand Down
25 changes: 2 additions & 23 deletions src/app/proposals/_components/proposal-list-item/_index.scss
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
@import "src/styles/vars_mixins";

.proposal-list-item {
padding: 16px;
border-radius: 10px;
margin-bottom: 16px;
box-shadow: $box-shadow-alt;

@include themify(day) {
@apply text-gray-steel bg-light-300;
}

@include themify(night) {
@apply text-white-500;
}

@include themify(night) {
@apply bg-dark-600;
}
@apply text-gray-steel dark:text-white-500 border border-[--border-color] rounded-2xl p-4 lg:p-6 xl:p-8;

&.voted-by-voter {
@include themify(day) {
@apply bg-blue-faded;
}

@include themify(night) {
@apply bg-tundora;
}
@apply bg-blue-faded dark:bg-tundora;
}

.item-content {
Expand Down
22 changes: 15 additions & 7 deletions src/app/proposals/_components/proposal-list-item/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React, { useMemo, useState } from "react";
import moment, { now } from "moment";
import numeral from "numeral";
Expand All @@ -11,6 +13,7 @@ import { parseAsset } from "@/utils";
import { ProposalVoteBtn, ProposalVotes } from "@/app/proposals/_components";
import { DEFAULT_DYNAMIC_PROPS, getDynamicPropsQuery, getProposalVotesQuery } from "@/api/queries";
import { useSearchParams } from "next/navigation";
import { Badge } from "@ui/badge";

interface Props {
proposal: Proposal;
Expand All @@ -22,12 +25,13 @@ export function ProposalListItem({ proposal, isReturnProposalId, thresholdPropos
const params = useSearchParams();
const [show, setShow] = useState(false);

const { data: votes, isLoading } = getProposalVotesQuery(
const { data: votesPages, isLoading } = getProposalVotesQuery(
proposal.proposal_id,
params.get("voter") ?? "",
1000
).useClientQuery();
const { data: dynamicProps } = getDynamicPropsQuery().useClientQuery();
const votes = useMemo(() => votesPages?.pages?.reduce((acc, page) => [...acc, ...page], []), []);

const votedByVoter = useMemo(
() => (votes?.length ?? 0) > 0 && votes?.[0].voter === params.get("voter"),
Expand Down Expand Up @@ -57,22 +61,26 @@ export function ProposalListItem({ proposal, isReturnProposalId, thresholdPropos
<div className="item-content">
<div className="left-side">
<div className="proposal-users-card">
<UserAvatar username={proposal.creator} size="small" />
<span className="users">
<div className="flex items-center gap-2">
{i18next.t("proposals.by")}{" "}
<ProfileLink username={proposal.creator}>
<span> {proposal.creator}</span>
<Badge className="!p-1 gap-1 !pr-1.5">
<UserAvatar username={proposal.creator} size="small" />
<span> {proposal.creator}</span>
</Badge>
</ProfileLink>
{proposal.receiver && proposal.receiver !== proposal.creator && (
<>
{" "}
{i18next.t("proposals.for")}{" "}
<ProfileLink username={proposal.receiver}>
<span> {proposal.receiver}</span>
<Badge className="!p-1 gap-1 !pr-1.5">
<UserAvatar username={proposal.receiver} size="small" />
<span> {proposal.receiver}</span>
</Badge>
</ProfileLink>
</>
)}
</span>
</div>
</div>
<div className="proposal-title">
<Link href={`/proposals/${proposal.id}`}>
Expand Down
3 changes: 2 additions & 1 deletion src/app/proposals/_components/proposal-vote-btn/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export function ProposalVoteBtn({ proposal }: Props) {
activeUser?.username ?? "",
1
).useClientQuery();
const votes = useMemo(() => data?.pages?.reduce((acc, page) => [...acc, ...page], []), []);
const voted = useMemo(
() => (data?.length ?? 0) > 0 && data?.[0].voter === activeUser?.username,
() => (votes?.length ?? 0) > 0 && votes?.[0].voter === activeUser?.username,
[activeUser?.username, data]
);

Expand Down
51 changes: 27 additions & 24 deletions src/app/proposals/_components/proposal-votes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React, { useMemo, useState } from "react";
import numeral from "numeral";
import "./_index.scss";
Expand All @@ -9,12 +11,7 @@ import i18next from "i18next";
import { Entry, Proposal } from "@/entities";
import { LinearProgress, ProfileLink, ProfilePopover, UserAvatar } from "@/features/shared";
import { accountReputation, parseAsset } from "@/utils";
import {
DEFAULT_DYNAMIC_PROPS,
getAccountsQuery,
getDynamicPropsQuery,
getProposalVotesQuery
} from "@/api/queries";
import { DEFAULT_DYNAMIC_PROPS, getDynamicPropsQuery, getProposalVotesQuery } from "@/api/queries";
import { Spinner } from "@ui/spinner";
import { Pagination } from "@/features/ui";

Expand All @@ -31,19 +28,20 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
const [page, setPage] = useState(1);

const { data: dynamicProps } = getDynamicPropsQuery().useClientQuery();
const { data: votes, isFetching } = getProposalVotesQuery(
proposal.proposal_id,
"",
1000
).useClientQuery();
const {
data: votesPages,
isFetching,
fetchNextPage
} = getProposalVotesQuery(proposal.proposal_id, "", 1000).useClientQuery();
const votes = useMemo(
() => votesPages?.pages?.reduce((acc, page) => [...acc, ...page], []),
[votesPages?.pages]
);

const usernames = useMemo(() => Array.from(new Set(votes?.map((x) => x.voter))), [votes]);
const { data: accounts, isFetching: isFetchingAccounts } =
getAccountsQuery(usernames).useClientQuery();
const voters = useMemo(
() =>
accounts
?.map((account) => {
votes
?.map(({ voterAccount: account }) => {
const hp =
(parseAsset(account.vesting_shares).amount *
(dynamicProps ?? DEFAULT_DYNAMIC_PROPS).hivePerMVests) /
Expand Down Expand Up @@ -72,7 +70,7 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
}
return b.totalHp > a.totalHp ? 1 : -1;
}),
[accounts, dynamicProps, searchText, sort]
[votes, dynamicProps, searchText, sort]
);

const paginatedVoters = useMemo(() => voters?.slice((page - 1) * 10, page * 10), [page, voters]);
Expand All @@ -82,10 +80,12 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
<ModalHeader closeButton={true} className="items-center">
<ModalTitle>
<span className="text-blue-dark-sky mr-2">
{isFetchingAccounts || isFetching ? (
{isFetching ? (
<Spinner className="inline-flex w-3.5 h-3.5" />
) : voters && voters.length >= 1000 ? (
"1000+"
) : (
accounts?.length
voters?.length
)}
</span>
<span>{i18next.t("proposals.votes-dialog-title", { n: proposal.id })}</span>
Expand All @@ -108,7 +108,7 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
</FormControl>
</div>
<ModalBody>
{isFetching && isFetchingAccounts && <LinearProgress />}
{isFetching && <LinearProgress />}

<div className="voters-list mb-4">
<List grid={true} inline={true} defer={true}>
Expand Down Expand Up @@ -149,9 +149,7 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
})
) : (
<div className="user-info">
{isFetching || isFetchingAccounts
? i18next.t("proposals.searching")
: i18next.t("proposals.no-results")}
{isFetching ? i18next.t("proposals.searching") : i18next.t("proposals.no-results")}
</div>
)}
</List>
Expand All @@ -160,7 +158,12 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
<Pagination
dataLength={voters?.length ?? 0}
pageSize={10}
onPageChange={setPage}
onPageChange={(p) => {
setPage(p);
if (voters?.length / 10 === p) {
fetchNextPage();
}
}}
page={page}
/>
</div>
Expand Down
26 changes: 0 additions & 26 deletions src/app/proposals/_page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -142,32 +142,6 @@
font-size: 1.2rem;
}
}

&.proposals-detail-page {
.proposal-list {
position: relative;

.btn-dismiss {
position: absolute;
right: 0;
top: -13px;
width: 26px;
height: 26px;
border-radius: 50%;
@apply bg-blue-metallic;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $box-shadow-alt;
@apply text-white;

svg {
height: 14px;
}
}
}
}

}


Expand Down
6 changes: 4 additions & 2 deletions src/features/ui/pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode, useEffect, useState } from "react";
import "./index.css";
import { useIsMobile } from "@/features/ui/util/use-is-mobile";
import { classNameObject } from "@/features/ui/util";
import { classNameObject, useFilteredProps } from "@/features/ui/util";
import i18next from "i18next";
import { arrowLeftSvg, arrowRightSvg } from "@/features/ui/svg";

Expand All @@ -13,9 +13,11 @@ function PageButton(
children: ReactNode;
}
) {
const filteredProps = useFilteredProps(props, ["active"]);

return (
<button
{...props}
{...filteredProps}
className={classNameObject({
"pagination border-r dark:border-gray-700 border-t border-b first:border-l first:rounded-l-xl last:rounded-r-xl last:border-l-0 disabled:hover:bg-white p-2.5 disabled:text-gray-600":
true,
Expand Down

0 comments on commit 1468913

Please sign in to comment.