{(voters?.length ?? 0) > 0 ? (
- voters?.map((x,index) => {
+ voters?.map((x, index) => {
const strHp = numeral(x.hp).format("0.00,");
const strProxyHp = numeral(x.proxyHp).format("0.00,");
@@ -131,7 +136,7 @@ export function ProposalVotes({ proposal, onHide }: ProposalVotesProps) {
})
) : (
- {isLoading ? i18next.t("proposals.searching") : i18next.t("proposals.no-results")}
+ {isFetching ? i18next.t("proposals.searching") : i18next.t("proposals.no-results")}
)}
diff --git a/src/app/submit/_api/publish.ts b/src/app/submit/_api/publish.ts
index b91cc98c1..c98586fe5 100644
--- a/src/app/submit/_api/publish.ts
+++ b/src/app/submit/_api/publish.ts
@@ -34,6 +34,7 @@ export function usePublishApi(onClear: () => void) {
const { videos, isNsfw, buildBody } = useThreeSpeakManager();
const { clearAll } = usePollsCreationManagement();
+ const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry();
return useMutation({
mutationKey: ["publish"],
@@ -152,7 +153,7 @@ export function usePublishApi(onClear: () => void) {
max_accepted_payout: options.max_accepted_payout,
percent_hbd: options.percent_hbd
};
- EcencyEntriesCacheManagement.updateEntryQueryData([entry]);
+ updateEntryQueryData([entry]);
success(i18next.t("submit.published"));
onClear();
diff --git a/src/app/submit/_api/update.ts b/src/app/submit/_api/update.ts
index c799ddfb2..a36b7b7b4 100644
--- a/src/app/submit/_api/update.ts
+++ b/src/app/submit/_api/update.ts
@@ -16,6 +16,8 @@ export function useUpdateApi(onClear: () => void) {
const { buildBody } = useThreeSpeakManager();
const router = useRouter();
+ const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry();
+
return useMutation({
mutationKey: ["update"],
mutationFn: async ({
@@ -74,7 +76,7 @@ export function useUpdateApi(onClear: () => void) {
json_metadata: jsonMeta,
updated: correctIsoDate(moment().toISOString())
};
- EcencyEntriesCacheManagement.updateEntryQueryData([entry]);
+ updateEntryQueryData([entry]);
onClear();
success(i18next.t("submit.updated"));
diff --git a/src/core/caches/entries-cache.tsx b/src/core/caches/entries-cache.tsx
index 570847fa1..12045a312 100644
--- a/src/core/caches/entries-cache.tsx
+++ b/src/core/caches/entries-cache.tsx
@@ -3,6 +3,7 @@ import * as bridgeApi from "../../api/bridge";
import dmca from "@/dmca.json";
import { Entry, EntryVote } from "@/entities";
import { makeEntryPath } from "@/utils";
+import { QueryClient, useQueryClient } from "@tanstack/react-query";
export namespace EcencyEntriesCacheManagement {
export function getEntryQueryByPath(author?: string, permlink?: string) {
@@ -12,8 +13,7 @@ export namespace EcencyEntriesCacheManagement {
author && permlink ? makeEntryPath("", author!!, permlink!!) : "EMPTY"
],
queryFn: () => bridgeApi.getPost(author, permlink),
- enabled: typeof author === "string" && typeof permlink === "string" && !!author && !!permlink,
- staleTime: Infinity
+ enabled: typeof author === "string" && typeof permlink === "string" && !!author && !!permlink
});
}
@@ -25,8 +25,7 @@ export namespace EcencyEntriesCacheManagement {
],
queryFn: () => bridgeApi.getPost(initialEntry?.author, initialEntry?.permlink) as Promise
,
initialData: initialEntry,
- enabled: !!initialEntry,
- staleTime: Infinity
+ enabled: !!initialEntry
});
}
@@ -41,41 +40,118 @@ export namespace EcencyEntriesCacheManagement {
});
}
- export function addReply(entry: Entry | undefined, reply: Entry) {
- return mutateEntryInstance(entry, (value) => ({
- ...value,
- children: value.children + 1,
- replies: [reply, ...value.replies]
- }));
+ export function useAddReply(initialEntry?: Entry) {
+ const qc = useQueryClient();
+
+ return {
+ addReply: (reply: Entry, entry = initialEntry) =>
+ mutateEntryInstance(
+ entry,
+ (value) => ({
+ ...value,
+ children: value.children + 1,
+ replies: [reply, ...value.replies]
+ }),
+ qc
+ )
+ };
}
- export function updateRepliesCount(entry: Entry, count: number) {
- return mutateEntryInstance(entry, (value) => ({
- ...value,
- children: count
- }));
+ export function useUpdateRepliesCount(initialEntry?: Entry) {
+ const qc = useQueryClient();
+
+ return {
+ updateRepliesCount: (count: number, entry = initialEntry) =>
+ mutateEntryInstance(
+ entry,
+ (value) => ({
+ ...value,
+ children: count
+ }),
+ qc
+ )
+ };
}
- export function updateVotes(entry: Entry, votes: EntryVote[], payout: number) {
- return mutateEntryInstance(entry, (value) => ({
- ...value,
- active_votes: votes,
- stats: { ...entry.stats, total_votes: votes.length, flag_weight: entry.stats.flag_weight },
- total_votes: votes.length,
- payout,
- pending_payout_value: String(payout)
- }));
+ export function useInvalidation(entry?: Entry) {
+ const qc = useQueryClient();
+ return {
+ invalidate: () =>
+ qc.invalidateQueries({
+ queryKey: [
+ QueryIdentifiers.ENTRY,
+ makeEntryPath("", entry?.author ?? "", entry?.permlink ?? "")
+ ]
+ })
+ };
}
- export function invalidate(entry: Entry) {
- return getQueryClient().invalidateQueries({
- queryKey: [QueryIdentifiers.ENTRY, makeEntryPath("", entry.author, entry.permlink)]
- });
+ export function useUpdateVotes(entry?: Entry) {
+ const qc = useQueryClient();
+ return {
+ update: (votes: EntryVote[], payout: number) =>
+ entry &&
+ mutateEntryInstance(
+ entry,
+ (value) => ({
+ ...value,
+ active_votes: votes,
+ stats: {
+ ...entry.stats,
+ total_votes: votes.length,
+ flag_weight: entry.stats.flag_weight
+ },
+ total_votes: votes.length,
+ payout,
+ pending_payout_value: String(payout)
+ }),
+ qc
+ )
+ };
+ }
+
+ export function useUpdateEntry() {
+ const qc = useQueryClient();
+
+ return {
+ updateEntryQueryData: (entries: Entry[]) =>
+ entries.forEach((entry) => {
+ qc.setQueryData(
+ [QueryIdentifiers.ENTRY, makeEntryPath("", entry.author, entry.permlink)],
+ () => {
+ const data = { ...entry };
+ if (
+ dmca.some((rx: string) => new RegExp(rx).test(`@${entry.author}/${entry.permlink}`))
+ ) {
+ data.body = "This post is not available due to a copyright/fraudulent claim.";
+ data.title = "";
+ }
+
+ return data;
+ }
+ );
+ })
+ };
+ }
+
+ export function useUpdateReblogsCount(entry: Entry) {
+ const qc = useQueryClient();
+ return {
+ update: (count: number) =>
+ mutateEntryInstance(
+ entry,
+ (value) => ({
+ ...value,
+ reblogs: count
+ }),
+ qc
+ )
+ };
}
- export function updateEntryQueryData(entries: Entry[]) {
+ function updateEntryQueryData(entries: Entry[], qc: QueryClient = getQueryClient()) {
entries.forEach((entry) => {
- getQueryClient().setQueryData(
+ qc.setQueryData(
[QueryIdentifiers.ENTRY, makeEntryPath("", entry.author, entry.permlink)],
() => {
const data = { ...entry };
@@ -92,24 +168,20 @@ export namespace EcencyEntriesCacheManagement {
});
}
- export function updateReblogsCount(entry: Entry, count: number) {
- console.log(entry, count);
- return mutateEntryInstance(entry, (value) => {
- value.reblogs = count;
- return value;
- });
- }
-
- function mutateEntryInstance(entry: Entry | undefined, callback: (value: Entry) => Entry) {
+ function mutateEntryInstance(
+ entry: Entry | undefined,
+ callback: (value: Entry) => Entry,
+ qc: QueryClient = getQueryClient()
+ ) {
if (!entry) {
throw new Error("Mutate entry instance – entry not provided");
}
- const actualEntryValue = getQueryClient().getQueryData([
+ const actualEntryValue = qc.getQueryData([
QueryIdentifiers.ENTRY,
makeEntryPath("", entry.author, entry.permlink)
]);
const value = callback(actualEntryValue ?? entry);
- return updateEntryQueryData([value]);
+ return updateEntryQueryData([value], qc);
}
}
diff --git a/src/core/react-query/index.ts b/src/core/react-query/index.ts
index 0fc8ca745..274b5e4bf 100644
--- a/src/core/react-query/index.ts
+++ b/src/core/react-query/index.ts
@@ -86,7 +86,8 @@ export enum QueryIdentifiers {
GET_FOLLOWING = "get-following",
HIVE_HBD_STATS = "hive-hbd-stats",
GET_ORDER_BOOK = "get-order-book",
- SIMILAR_ENTRIES = "similar-entries"
+ SIMILAR_ENTRIES = "similar-entries",
+ SEARCH_BY_USERNAME = "search-by-username"
}
export function makeQueryClient() {
@@ -104,13 +105,14 @@ export function makeQueryClient() {
});
}
-let browserQueryClient: QueryClient | undefined = undefined;
+export let BROWSER_QUERY_CLIENT: QueryClient | undefined = undefined;
+
+export function setBrowserQueryClient(queryClient: QueryClient) {
+ BROWSER_QUERY_CLIENT = queryClient;
+}
export const getQueryClient = isServer
? cache(() => makeQueryClient())
- : () => {
- if (!browserQueryClient) browserQueryClient = makeQueryClient();
- return browserQueryClient;
- };
+ : () => BROWSER_QUERY_CLIENT!;
export * from "./ecency-queries-manager";
diff --git a/src/features/next-middleware/handle-entries-redirect.ts b/src/features/next-middleware/handle-entries-redirect.ts
index bbaa2cc06..ee9bf8acd 100644
--- a/src/features/next-middleware/handle-entries-redirect.ts
+++ b/src/features/next-middleware/handle-entries-redirect.ts
@@ -11,7 +11,8 @@ export function isEntriesRedirect(request: NextRequest) {
"spk",
"engine",
"settings",
- "feed"
+ "feed",
+ "referrals"
];
const isAnySection = profileSections.some((section) => request.url.includes(section));
const isEntryLikePage = /\/(@.+)\/(.+)/gm.test(request.nextUrl.pathname);
diff --git a/src/features/shared/boost/index.tsx b/src/features/shared/boost/index.tsx
index 89d8cde33..6d2eefe8e 100644
--- a/src/features/shared/boost/index.tsx
+++ b/src/features/shared/boost/index.tsx
@@ -61,7 +61,7 @@ export function BoostDialog({ onHide }: Props) {
);
useEffect(() => {
- if (prices!.length > 0) {
+ if (prices && prices.length > 0) {
setDuration(prices![0].duration);
}
}, [prices]);
@@ -126,7 +126,7 @@ export function BoostDialog({ onHide }: Props) {
className={`balance-input ${balanceError ? "is-invalid" : ""}`}
plaintext={true}
readOnly={true}
- value={`${activeUserPoints?.points} POINTS`}
+ value={`${activeUserPoints?.points ?? "0.000"} POINTS`}
/>
{balanceError && {balanceError}}
{isAlreadyBoosted && (
diff --git a/src/features/shared/discussion/discussion-item.tsx b/src/features/shared/discussion/discussion-item.tsx
index 8f73e8ec5..55f0e0a49 100644
--- a/src/features/shared/discussion/discussion-item.tsx
+++ b/src/features/shared/discussion/discussion-item.tsx
@@ -60,6 +60,8 @@ export function DiscussionItem({
const { data: mutedUsers } = getMutedUsersQuery(activeUser).useClientQuery();
const { data: botsList } = getBotsQuery().useClientQuery();
+ const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry();
+
const readMore = useMemo(() => (entry ? entry.children > 0 && entry.depth > 5 : false), [entry]);
const showSubList = useMemo(
() => (entry ? !readMore && entry.children > 0 : false),
@@ -266,7 +268,7 @@ export function DiscussionItem({
EcencyEntriesCacheManagement.updateEntryQueryData([entry])}
+ onSuccess={(entry) => updateEntryQueryData([entry])}
/>
)}
diff --git a/src/features/shared/discussion/index.tsx b/src/features/shared/discussion/index.tsx
index 116e4c3fa..e6e506216 100644
--- a/src/features/shared/discussion/index.tsx
+++ b/src/features/shared/discussion/index.tsx
@@ -17,6 +17,7 @@ import { LinearProgress, LoginRequired } from "@/features/shared";
import { getBotsQuery } from "@/api/queries";
import { SortOrder } from "@/enums";
import { getDiscussionsQuery } from "@/api/queries/get-discussions-query";
+import { EcencyEntriesCacheManagement } from "@/core/caches";
setProxyBase(defaults.imageServer);
@@ -33,6 +34,7 @@ export function Discussion({ hideControls, isRawContent, parent, community }: Pr
const [visible, setVisible] = useState(false);
const [order, setOrder] = useState(SortOrder.trending);
+ const { updateEntryQueryData } = EcencyEntriesCacheManagement.useUpdateEntry();
const { isLoading, data } = getDiscussionsQuery(
parent,
@@ -66,6 +68,10 @@ export function Discussion({ hideControls, isRawContent, parent, community }: Pr
}, [isRawContent, previousIsRawContent]);
useEffect(() => setVisible(!!activeUser), [activeUser]);
+ useEffect(() => {
+ updateEntryQueryData(Array.from(Object.values(data ?? [])));
+ }, [data]);
+
const show = () => setVisible(true);
const join = (
diff --git a/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx b/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx
index dd3371611..bbbb793dd 100644
--- a/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx
+++ b/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx
@@ -10,7 +10,7 @@ import { useGlobalStore } from "@/core/global-store";
import { InputVote } from "@ui/input";
import { parseAsset } from "@/utils";
import { votingPower } from "@/api/hive";
-import { getDynamicPropsQuery } from "@/api/queries";
+import { DEFAULT_DYNAMIC_PROPS, getDynamicPropsQuery } from "@/api/queries";
import { Account, Entry } from "@/entities";
import { Spinner } from "@ui/spinner";
@@ -126,7 +126,8 @@ export function EntryVoteDialog({
return 0;
}
- const { fundRecentClaims, fundRewardBalance, base, quote } = dynamicProps!;
+ const { fundRecentClaims, fundRewardBalance, base, quote } =
+ dynamicProps ?? DEFAULT_DYNAMIC_PROPS;
const { data: account } = activeUser;
if (!account.__loaded) {
diff --git a/src/features/shared/search-by-username.tsx b/src/features/shared/search-by-username.tsx
index 859ea63b2..996fcebf0 100644
--- a/src/features/shared/search-by-username.tsx
+++ b/src/features/shared/search-by-username.tsx
@@ -1,13 +1,12 @@
-import React, { useCallback, useEffect, useState } from "react";
+import React, { useEffect, useState } from "react";
import { FormControl, InputGroup } from "@ui/input";
import { Spinner } from "@ui/spinner";
import { useGlobalStore } from "@/core/global-store";
-import { lookupAccounts } from "@/api/hive";
-import { formatError } from "@/api/operations";
-import { error } from "@/features/shared/feedback";
import { UserAvatar } from "@/features/shared/user-avatar";
import { SuggestionList } from "@/features/shared/suggestion-list";
import i18next from "i18next";
+import { useSearchByUsernameQuery } from "@/api/queries";
+import { useDebounce } from "react-use";
interface Props {
username?: string;
@@ -21,10 +20,16 @@ export const SearchByUsername = ({ setUsername, excludeActiveUser, recent, usern
const [prefilledUsername, setPrefilledUsername] = useState(username || "");
const [usernameInput, setUsernameInput] = useState(username || "");
- const [usernameData, setUsernameData] = useState([]);
const [isActiveUserSet, setIsActiveUserSet] = useState(false);
- const [timer, setTimer] = useState(null);
- const [isUsernameDataLoading, setIsUsernameDataLoading] = useState(false);
+
+ const [query, setQuery] = useState("");
+
+ const { data: usernameData, isLoading: isUsernameDataLoading } = useSearchByUsernameQuery(
+ query,
+ !isActiveUserSet
+ );
+
+ useDebounce(() => setQuery(usernameInput), 500, [usernameInput]);
useEffect(() => {
if (activeUser && !excludeActiveUser) {
@@ -34,53 +39,11 @@ export const SearchByUsername = ({ setUsername, excludeActiveUser, recent, usern
}
}, [activeUser, excludeActiveUser, setUsername]);
- const getUsernameData = useCallback(
- async (query: string) => {
- try {
- const resp = await lookupAccounts(query, 5);
- if (resp) {
- setUsernameData(
- resp.filter((item) => (excludeActiveUser ? item !== activeUser?.username : true))
- );
- }
- } catch (e) {
- error(...formatError(e));
- } finally {
- setIsUsernameDataLoading(false);
- }
- },
- [activeUser?.username, excludeActiveUser]
- );
- const fetchUsernameData = useCallback(
- (query: string) => {
- if (timer) {
- clearTimeout(timer);
- }
-
- if (usernameInput === "" || isActiveUserSet) {
- setIsActiveUserSet(false);
- setIsUsernameDataLoading(false);
- return;
- }
-
- setIsUsernameDataLoading(true);
- setTimer(setTimeout(() => getUsernameData(query), 500));
- },
- [getUsernameData, isActiveUserSet, timer, usernameInput]
- );
-
useEffect(() => {
- if (!usernameInput) {
- setUsername("");
- if (recent) {
- setUsernameData(recent);
- }
- }
if (usernameInput !== prefilledUsername) {
- fetchUsernameData(usernameInput);
setPrefilledUsername("");
}
- }, [fetchUsernameData, prefilledUsername, recent, setUsername, usernameInput]);
+ }, [prefilledUsername, usernameInput]);
const suggestionProps = {
renderer: (i: any) => {
@@ -99,7 +62,7 @@ export const SearchByUsername = ({ setUsername, excludeActiveUser, recent, usern
return (
diff --git a/src/features/shared/transactions/transaction-row.tsx b/src/features/shared/transactions/transaction-row.tsx
index e99b830a5..840925c44 100644
--- a/src/features/shared/transactions/transaction-row.tsx
+++ b/src/features/shared/transactions/transaction-row.tsx
@@ -17,7 +17,7 @@ import { dateToFullRelative, formattedNumber, parseAsset, vestsToHp } from "@/ut
import i18next from "i18next";
import { EntryLink, UserAvatar } from "@/features/shared";
import { TwoUserAvatar } from "@/features/shared/two-user-avatar";
-import { getDynamicPropsQuery } from "@/api/queries";
+import { DEFAULT_DYNAMIC_PROPS, getDynamicPropsQuery } from "@/api/queries";
import { Transaction } from "@/entities";
interface Props {
@@ -29,7 +29,7 @@ interface Props {
export function TransactionRow({ entry, transaction: item }: Props) {
const { data: dynamicProps } = getDynamicPropsQuery().useClientQuery();
- const { hivePerMVests } = dynamicProps!;
+ const { hivePerMVests } = dynamicProps ?? DEFAULT_DYNAMIC_PROPS;
const tr = item || entry;
let flag = false;
diff --git a/src/features/shared/transfer/transfer-step-1.tsx b/src/features/shared/transfer/transfer-step-1.tsx
index 52eb896c7..941b22c91 100644
--- a/src/features/shared/transfer/transfer-step-1.tsx
+++ b/src/features/shared/transfer/transfer-step-1.tsx
@@ -14,7 +14,12 @@ import {
vestsToHp
} from "@/utils";
import { useGlobalStore } from "@/core/global-store";
-import { getDynamicPropsQuery, getPointsQuery, getTransactionsQuery } from "@/api/queries";
+import {
+ DEFAULT_DYNAMIC_PROPS,
+ getDynamicPropsQuery,
+ getPointsQuery,
+ getTransactionsQuery
+} from "@/api/queries";
import { TransferFormText } from "@/features/shared/transfer/transfer-form-text";
import { TransferAssetSwitch } from "@/features/shared/transfer/transfer-assets-switch";
import { EXCHANGE_ACCOUNTS } from "@/consts";
@@ -64,7 +69,7 @@ export function TransferStep1({ titleLngKey }: Props) {
[transactions]
);
const w = useMemo(
- () => new HiveWallet(activeUser!.data, dynamicProps!),
+ () => new HiveWallet(activeUser!.data, dynamicProps ?? DEFAULT_DYNAMIC_PROPS),
[activeUser, dynamicProps]
);
const subTitleLngKey = useMemo(() => `${mode}-sub-title`, [mode]);
@@ -149,7 +154,7 @@ export function TransferStep1({ titleLngKey }: Props) {
if (asset === "POINT") {
return parseAsset(activeUserPoints?.points ?? "0.0").amount;
}
- const w = new HiveWallet(activeUser!.data, dynamicProps!);
+ const w = new HiveWallet(activeUser!.data, dynamicProps ?? DEFAULT_DYNAMIC_PROPS);
if (mode === "withdraw-saving" || mode === "claim-interest") {
return asset === "HIVE" ? w.savingBalance : w.savingBalanceHbd;
@@ -164,7 +169,7 @@ export function TransferStep1({ titleLngKey }: Props) {
}
if (asset === "HP") {
- const { hivePerMVests } = dynamicProps!;
+ const { hivePerMVests } = dynamicProps ?? DEFAULT_DYNAMIC_PROPS;
const vestingShares = w.vestingSharesAvailable;
return vestsToHp(vestingShares, hivePerMVests);
}
diff --git a/src/features/shared/transfer/transfer-step-2.tsx b/src/features/shared/transfer/transfer-step-2.tsx
index 740ba01c8..891663ae9 100644
--- a/src/features/shared/transfer/transfer-step-2.tsx
+++ b/src/features/shared/transfer/transfer-step-2.tsx
@@ -7,7 +7,7 @@ import React, { useMemo } from "react";
import { useTransferSharedState } from "./transfer-shared-state";
import { useGlobalStore } from "@/core/global-store";
import { hpToVests } from "@/features/shared/transfer/hp-to-vests";
-import { getDynamicPropsQuery } from "@/api/queries";
+import { DEFAULT_DYNAMIC_PROPS, getDynamicPropsQuery } from "@/api/queries";
interface Props {
titleLngKey: string;
@@ -48,7 +48,7 @@ export function TransferStep2({ titleLngKey }: Props) {
{asset === "HP" && (
- {hpToVests(Number(amount), dynamicProps!.hivePerMVests)}
+ {hpToVests(Number(amount), (dynamicProps ?? DEFAULT_DYNAMIC_PROPS).hivePerMVests)}
)}
{memo &&