From 7f59dc7b7b619e4f4c3480368ae1a37d108eb502 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Fri, 27 Oct 2023 21:03:01 +0400 Subject: [PATCH 01/35] wip --- .../js/Pages/Galleries/MyGalleries/Create.tsx | 8 ++- .../Pages/Galleries/hooks/useGalleryDraft.ts | 70 +++++++++++++++++++ resources/types/generated.d.ts | 4 -- 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDraft.ts diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 2d12ef6a6..ac4be9e0a 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -17,6 +17,7 @@ import { NoNftsOverlay } from "@/Components/Layout/NoNftsOverlay"; import { useMetaMaskContext } from "@/Contexts/MetaMaskContext"; import { useAuthorizedAction } from "@/Hooks/useAuthorizedAction"; import { GalleryNameInput } from "@/Pages/Galleries/Components/GalleryNameInput"; +import { useGalleryDraft } from "@/Pages/Galleries/hooks/useGalleryDraft"; import { useGalleryForm } from "@/Pages/Galleries/hooks/useGalleryForm"; import { assertUser, assertWallet } from "@/Utils/assertions"; import { isTruthy } from "@/Utils/is-truthy"; @@ -61,6 +62,8 @@ const Create = ({ gallery, }); + const { setCover, setTitle, setNfts } = useGalleryDraft(); + const totalValue = 0; assertUser(auth.user); @@ -155,7 +158,10 @@ const Create = ({ }} > { + updateSelectedNfts(nfts); + setNfts(nfts); + }} error={errors.nfts} /> diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts b/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts new file mode 100644 index 000000000..65a8709db --- /dev/null +++ b/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts @@ -0,0 +1,70 @@ +import { useRef } from "react"; +import { useAuth } from "@/Contexts/AuthContext"; + +interface DraftNft { + nftId: number; + image: string; + collectionSlug: slug; +} + +interface GalleryDraft { + title: string | null; + cover: string | null; + nfts: DraftNft[]; +} + +const initialGalleryDraft: GalleryDraft = { + title: null, + cover: null, + nfts: [], +}; + +export const useGalleryDraft = (givenDraftId?: string) => { + const { wallet } = useAuth(); + + const draftIdReference = useRef(givenDraftId ?? `gallery-${wallet?.address}-${new Date().getTime()}`); + + const draftId = draftIdReference.current; + + const getDraft = (): GalleryDraft => { + try { + const rawDraft = localStorage.getItem(draftId); + return rawDraft != null ? (JSON.parse(rawDraft) as GalleryDraft) : initialGalleryDraft; + } catch (error) { + return initialGalleryDraft; + } + }; + + const draftReference = useRef(getDraft()); + + const draft = draftReference.current; + + const persistDraft = (): void => { + localStorage.setItem(draftId, JSON.stringify(draft)); + }; + + const setTitle = (title: string | null): void => { + draft.title = title; + persistDraft(); + }; + + const setCover = (image: string | null): void => { + draft.cover = image; + persistDraft(); + }; + + const setNfts = (nfts: App.Data.Gallery.GalleryNftData[]) => { + draft.nfts = nfts.map((nft) => ({ + nftId: nft.id, + image: nft.images.large ?? "", + collectionSlug: nft.collectionSlug, + })); + persistDraft(); + }; + + return { + setTitle, + setCover, + setNfts, + }; +}; diff --git a/resources/types/generated.d.ts b/resources/types/generated.d.ts index 212beec63..157bb0fa6 100644 --- a/resources/types/generated.d.ts +++ b/resources/types/generated.d.ts @@ -242,10 +242,6 @@ declare namespace App.Data.Gallery { isOwner: boolean; hasLiked: boolean; }; - export type GalleryLikeData = { - likes: number; - hasLiked: boolean; - }; export type GalleryNftData = { id: number; name: string | null; From beba28b476325775bac76d5f06bbe5167b95ccbc Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 17:21:20 +0400 Subject: [PATCH 02/35] wip --- package.json | 1 + pnpm-lock.yaml | 21 ++- .../js/Pages/Galleries/MyGalleries/Create.tsx | 20 +-- .../Pages/Galleries/hooks/useGalleryDraft.ts | 70 ---------- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 127 ++++++++++++++++++ .../Pages/Galleries/hooks/useGalleryForm.ts | 23 +++- resources/js/app.tsx | 4 + resources/js/databaseConfig.ts | 16 +++ 8 files changed, 197 insertions(+), 85 deletions(-) delete mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDraft.ts create mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts create mode 100644 resources/js/databaseConfig.ts diff --git a/package.json b/package.json index b61f75e72..761d33187 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "react-chartjs-2": "^5.2.0", "react-i18next": "^12.3.1", "react-in-viewport": "1.0.0-alpha.30", + "react-indexed-db-hook": "^1.0.14", "react-loading-skeleton": "^3.3.1", "react-popper": "^2.3.0", "react-resize-detector": "^8.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bed7746bc..10266bd51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - dependencies: '@ardenthq/sdk-helpers': specifier: ^1.2.7 @@ -71,6 +67,9 @@ dependencies: react-in-viewport: specifier: 1.0.0-alpha.30 version: 1.0.0-alpha.30(react-dom@18.2.0)(react@18.2.0) + react-indexed-db-hook: + specifier: ^1.0.14 + version: 1.0.14(react-dom@18.2.0)(react@18.2.0) react-loading-skeleton: specifier: ^3.3.1 version: 3.3.1(react@18.2.0) @@ -10351,6 +10350,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-indexed-db-hook@1.0.14(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-tQ6rWofgXUCBhZp9pRpWzthzPbjqcll5uXMo07lbQTKl47VyL9nw9wfVswRxxzS5yj5Sq/VHUkNUjamWbA/M/w==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-inspector@6.0.2(react@18.2.0): resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} peerDependencies: @@ -12595,3 +12604,7 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index ac4be9e0a..31bf2b8e3 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -17,7 +17,6 @@ import { NoNftsOverlay } from "@/Components/Layout/NoNftsOverlay"; import { useMetaMaskContext } from "@/Contexts/MetaMaskContext"; import { useAuthorizedAction } from "@/Hooks/useAuthorizedAction"; import { GalleryNameInput } from "@/Pages/Galleries/Components/GalleryNameInput"; -import { useGalleryDraft } from "@/Pages/Galleries/hooks/useGalleryDraft"; import { useGalleryForm } from "@/Pages/Galleries/hooks/useGalleryForm"; import { assertUser, assertWallet } from "@/Utils/assertions"; import { isTruthy } from "@/Utils/is-truthy"; @@ -58,11 +57,10 @@ const Create = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [busy, setBusy] = useState(false); - const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing } = useGalleryForm({ - gallery, - }); - - const { setCover, setTitle, setNfts } = useGalleryDraft(); + const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing, setDraftCover } = + useGalleryForm({ + gallery, + }); const totalValue = 0; @@ -158,10 +156,7 @@ const Create = ({ }} > { - updateSelectedNfts(nfts); - setNfts(nfts); - }} + onChange={updateSelectedNfts} error={errors.nfts} /> @@ -204,8 +199,13 @@ const Create = ({ setGalleryCoverImageUrl(imageDataURI); if (blob === undefined) { setData("coverImage", null); + setDraftCover(null); } else { setData("coverImage", new File([blob], blob.name, { type: blob.type })); + // eslint ignore + void blob.arrayBuffer().then((buf) => { + setDraftCover(buf); + }); } setIsGalleryFormSliderOpen(false); }} diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts b/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts deleted file mode 100644 index 65a8709db..000000000 --- a/resources/js/Pages/Galleries/hooks/useGalleryDraft.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useRef } from "react"; -import { useAuth } from "@/Contexts/AuthContext"; - -interface DraftNft { - nftId: number; - image: string; - collectionSlug: slug; -} - -interface GalleryDraft { - title: string | null; - cover: string | null; - nfts: DraftNft[]; -} - -const initialGalleryDraft: GalleryDraft = { - title: null, - cover: null, - nfts: [], -}; - -export const useGalleryDraft = (givenDraftId?: string) => { - const { wallet } = useAuth(); - - const draftIdReference = useRef(givenDraftId ?? `gallery-${wallet?.address}-${new Date().getTime()}`); - - const draftId = draftIdReference.current; - - const getDraft = (): GalleryDraft => { - try { - const rawDraft = localStorage.getItem(draftId); - return rawDraft != null ? (JSON.parse(rawDraft) as GalleryDraft) : initialGalleryDraft; - } catch (error) { - return initialGalleryDraft; - } - }; - - const draftReference = useRef(getDraft()); - - const draft = draftReference.current; - - const persistDraft = (): void => { - localStorage.setItem(draftId, JSON.stringify(draft)); - }; - - const setTitle = (title: string | null): void => { - draft.title = title; - persistDraft(); - }; - - const setCover = (image: string | null): void => { - draft.cover = image; - persistDraft(); - }; - - const setNfts = (nfts: App.Data.Gallery.GalleryNftData[]) => { - draft.nfts = nfts.map((nft) => ({ - nftId: nft.id, - image: nft.images.large ?? "", - collectionSlug: nft.collectionSlug, - })); - persistDraft(); - }; - - return { - setTitle, - setCover, - setNfts, - }; -}; diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts new file mode 100644 index 000000000..8f1a2a3bf --- /dev/null +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -0,0 +1,127 @@ +import { useEffect, useState } from "react"; +import { useIndexedDB } from "react-indexed-db-hook"; +import { useAuth } from "@/Contexts/AuthContext"; +import { useDebounce } from "@/Hooks/useDebounce"; + +interface DraftNft { + nftId: number; + image: string; + collectionSlug: string; +} + +interface GalleryDraft { + title: string; + cover: ArrayBuffer | null; + nfts: DraftNft[]; + walletAddress?: string; +} + +const initialGalleryDraft: GalleryDraft = { + title: "", + cover: null, + nfts: [], +}; + +export const useGalleryDrafts = (givenDraftId?: number) => { + const { wallet } = useAuth(); + + const database = useIndexedDB("gallery-drafts"); + + const [draft, setDraft] = useState(null); + const [draftId, setDraftId] = useState(givenDraftId ?? null); + + const [save, setSave] = useState(false); + const [isSaving, setIsSaving] = useState(false); + + const [title, setTitle] = useState(""); + const [debouncedValue] = useDebounce(title, 400); + + // populate `draft` state if `draftId` is present + useEffect(() => { + if (draft === null && draftId !== null) { + const getDraft = async (): Promise => { + const draft: GalleryDraft = await database.getByID(draftId); + console.log(draft); + setDraft(draft); + }; + + void getDraft(); + } + }, []); + + // persist debounced title + useEffect(() => { + if (draft === null) return; + + setDraft({ ...draft, title: debouncedValue }); + setSave(true); + }, [debouncedValue]); + + // initialize an initial `draft` state + useEffect(() => { + if (save && draftId === null) { + const initializeDraft = async (): Promise => { + const data: GalleryDraft = { + ...initialGalleryDraft, + walletAddress: wallet?.address, + }; + + const id = await database.add(data); + + setDraftId(id); + setDraft(data); + }; + + void initializeDraft(); + } + }, [save, draftId]); + + // update persisted draft + useEffect(() => { + if (!save || draftId === null || isSaving) return; + + setIsSaving(true); + + const saveDraft = async (): Promise => { + await database.update({ + ...draft, + id: draftId, + }); + setSave(false); + setIsSaving(false); + }; + + void saveDraft(); + }, [save]); + + const setDraftTitle = (title: string): void => { + setTitle(title); + }; + + const setDraftCover = (image: ArrayBuffer | null): void => { + draft != null && setDraft({ ...draft, cover: image }); + setSave(true); + }; + + const setDraftNfts = (nfts: App.Data.Gallery.GalleryNftData[]): void => { + draft != null && + setDraft({ + ...draft, + nfts: nfts.map((nft) => ({ + nftId: nft.id, + image: nft.images.large ?? "", + collectionSlug: nft.collectionSlug, + })), + }); + + setSave(true); + }; + + return { + draftId, + draft, + setDraftCover, + setDraftNfts, + setDraftTitle, + }; +}; diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index 402647db9..2ba699a56 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -1,8 +1,11 @@ import { useForm } from "@inertiajs/react"; -import { type FormEvent, useState } from "react"; +import { type FormEvent, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useToasts } from "@/Hooks/useToasts"; +import { useGalleryDrafts } from "@/Pages/Galleries/hooks/useGalleryDrafts"; +import { getQueryParameters } from "@/Utils/get-query-parameters"; import { isTruthy } from "@/Utils/is-truthy"; +import { replaceUrlQuery } from "@/Utils/replace-url-query"; interface UseGalleryFormProperties extends Record { id: number | null; @@ -24,11 +27,22 @@ export const useGalleryForm = ({ errors: Partial>; updateSelectedNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; processing: boolean; + setDraftCover: (image: ArrayBuffer | null) => void; } => { const { t } = useTranslation(); const [selectedNfts, setSelectedNfts] = useState([]); const { showToast } = useToasts(); + const { draftId: givenDraftId } = getQueryParameters(); + + const { setDraftCover, setDraftTitle, setDraftNfts, draftId } = useGalleryDrafts( + givenDraftId !== "" ? Number(givenDraftId) : undefined, + ); + + useEffect(() => { + draftId != null && replaceUrlQuery({ draftId: draftId.toString() }); + }, [draftId]); + const { data, setData, post, processing, errors, ...form } = useForm({ id: gallery?.id ?? null, name: gallery?.name ?? "", @@ -99,6 +113,8 @@ export const useGalleryForm = ({ "nfts", nfts.map((nft) => nft.id), ); + + setDraftNfts(nfts); }; return { @@ -108,12 +124,17 @@ export const useGalleryForm = ({ submit, errors, processing, + setDraftCover, setData: (field, value) => { setData(field, value); if (field === "name" && validateName(field)) { form.setError("name", ""); } + + if (field === "name") { + setDraftTitle(typeof value === "string" ? value : ""); + } }, }; }; diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 74bf4c2c4..05742e7df 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -21,12 +21,14 @@ import { import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; import { createRoot } from "react-dom/client"; import { I18nextProvider } from "react-i18next"; +import { initDB } from "react-indexed-db-hook"; import { AuthContextProvider } from "./Contexts/AuthContext"; import DarkModeContextProvider from "./Contexts/DarkModeContex"; import EnvironmentContextProvider from "./Contexts/EnvironmentContext"; import { CookieConsent } from "./cookieConsent"; import MetaMaskContextProvider from "@/Contexts/MetaMaskContext"; import { TransactionSliderProvider } from "@/Contexts/TransactionSliderContext"; +import { databaseConfig } from "@/databaseConfig"; import { i18n } from "@/I18n"; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access @@ -55,6 +57,8 @@ const appName = window.document.querySelector("title")?.innerText ?? "Dashbrd"; const queryClient = new QueryClient(); +initDB(databaseConfig); + void createInertiaApp({ title: (title) => (title !== "" ? title : appName), diff --git a/resources/js/databaseConfig.ts b/resources/js/databaseConfig.ts new file mode 100644 index 000000000..2a69fbfbd --- /dev/null +++ b/resources/js/databaseConfig.ts @@ -0,0 +1,16 @@ +export const databaseConfig = { + name: "dashbrd", + version: 1, + objectStoresMeta: [ + { + store: "gallery-drafts", + storeConfig: { keyPath: "id", autoIncrement: true }, + storeSchema: [ + { name: "walletAddress", keypath: "walletAddress", options: { unique: false } }, + { name: "title", keypath: "title", options: { unique: false } }, + { name: "cover", keypath: "cover", options: { unique: false } }, + { name: "nfts", keypath: "nfts", options: { unique: false } }, + ], + }, + ], +}; From 2b2b96a0657803f8c44b8e1bfe93bc0f379701f1 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 18:41:33 +0400 Subject: [PATCH 03/35] wip --- .../js/Pages/Galleries/MyGalleries/Create.tsx | 4 +- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 90 +++++++------------ .../Pages/Galleries/hooks/useGalleryForm.ts | 10 +-- 3 files changed, 40 insertions(+), 64 deletions(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 31bf2b8e3..aef36be03 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -199,12 +199,12 @@ const Create = ({ setGalleryCoverImageUrl(imageDataURI); if (blob === undefined) { setData("coverImage", null); - setDraftCover(null); + void setDraftCover(null); } else { setData("coverImage", new File([blob], blob.name, { type: blob.type })); // eslint ignore void blob.arrayBuffer().then((buf) => { - setDraftCover(buf); + void setDraftCover(buf); }); } setIsGalleryFormSliderOpen(false); diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 8f1a2a3bf..c99ca807c 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -14,12 +14,14 @@ interface GalleryDraft { cover: ArrayBuffer | null; nfts: DraftNft[]; walletAddress?: string; + id: number | null; } const initialGalleryDraft: GalleryDraft = { title: "", cover: null, nfts: [], + id: null, }; export const useGalleryDrafts = (givenDraftId?: number) => { @@ -27,10 +29,11 @@ export const useGalleryDrafts = (givenDraftId?: number) => { const database = useIndexedDB("gallery-drafts"); - const [draft, setDraft] = useState(null); - const [draftId, setDraftId] = useState(givenDraftId ?? null); + const [draft, setDraft] = useState({ + ...initialGalleryDraft, + walletAddress: wallet?.address, + }); - const [save, setSave] = useState(false); const [isSaving, setIsSaving] = useState(false); const [title, setTitle] = useState(""); @@ -38,10 +41,9 @@ export const useGalleryDrafts = (givenDraftId?: number) => { // populate `draft` state if `draftId` is present useEffect(() => { - if (draft === null && draftId !== null) { + if (draft.id === null && givenDraftId != null) { const getDraft = async (): Promise => { - const draft: GalleryDraft = await database.getByID(draftId); - console.log(draft); + const draft: GalleryDraft = await database.getByID(givenDraftId); setDraft(draft); }; @@ -51,74 +53,48 @@ export const useGalleryDrafts = (givenDraftId?: number) => { // persist debounced title useEffect(() => { - if (draft === null) return; - setDraft({ ...draft, title: debouncedValue }); - setSave(true); + void saveDraft(); }, [debouncedValue]); - // initialize an initial `draft` state - useEffect(() => { - if (save && draftId === null) { - const initializeDraft = async (): Promise => { - const data: GalleryDraft = { - ...initialGalleryDraft, - walletAddress: wallet?.address, - }; - - const id = await database.add(data); - - setDraftId(id); - setDraft(data); - }; - - void initializeDraft(); - } - }, [save, draftId]); - - // update persisted draft - useEffect(() => { - if (!save || draftId === null || isSaving) return; + const saveDraft = async (): Promise => { + if (isSaving) return; setIsSaving(true); - const saveDraft = async (): Promise => { - await database.update({ - ...draft, - id: draftId, - }); - setSave(false); - setIsSaving(false); - }; + if (draft.id === null) { + const id = await database.add(draft); + setDraft({ ...draft, id }); + } else { + await database.update(draft); + } - void saveDraft(); - }, [save]); + setIsSaving(false); + }; const setDraftTitle = (title: string): void => { setTitle(title); }; - const setDraftCover = (image: ArrayBuffer | null): void => { - draft != null && setDraft({ ...draft, cover: image }); - setSave(true); + const setDraftCover = async (image: ArrayBuffer | null): Promise => { + setDraft({ ...draft, cover: image }); + await saveDraft(); }; - const setDraftNfts = (nfts: App.Data.Gallery.GalleryNftData[]): void => { - draft != null && - setDraft({ - ...draft, - nfts: nfts.map((nft) => ({ - nftId: nft.id, - image: nft.images.large ?? "", - collectionSlug: nft.collectionSlug, - })), - }); - - setSave(true); + const setDraftNfts = async (nfts: App.Data.Gallery.GalleryNftData[]): Promise => { + setDraft({ + ...draft, + nfts: nfts.map((nft) => ({ + nftId: nft.id, + image: nft.images.large ?? "", + collectionSlug: nft.collectionSlug, + })), + }); + + await saveDraft(); }; return { - draftId, draft, setDraftCover, setDraftNfts, diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index 2ba699a56..1e3fa6254 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -27,7 +27,7 @@ export const useGalleryForm = ({ errors: Partial>; updateSelectedNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; processing: boolean; - setDraftCover: (image: ArrayBuffer | null) => void; + setDraftCover: (image: ArrayBuffer | null) => Promise; } => { const { t } = useTranslation(); const [selectedNfts, setSelectedNfts] = useState([]); @@ -35,13 +35,13 @@ export const useGalleryForm = ({ const { draftId: givenDraftId } = getQueryParameters(); - const { setDraftCover, setDraftTitle, setDraftNfts, draftId } = useGalleryDrafts( + const { setDraftCover, setDraftTitle, setDraftNfts, draft } = useGalleryDrafts( givenDraftId !== "" ? Number(givenDraftId) : undefined, ); useEffect(() => { - draftId != null && replaceUrlQuery({ draftId: draftId.toString() }); - }, [draftId]); + draft.id != null && replaceUrlQuery({ draftId: draft.id.toString() }); + }, [draft.id]); const { data, setData, post, processing, errors, ...form } = useForm({ id: gallery?.id ?? null, @@ -114,7 +114,7 @@ export const useGalleryForm = ({ nfts.map((nft) => nft.id), ); - setDraftNfts(nfts); + void setDraftNfts(nfts); }; return { From 694e033aabf5a502b7235a8da13462042909401c Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 18:55:07 +0400 Subject: [PATCH 04/35] wip --- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index c99ca807c..ebcd3a4ae 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { useIndexedDB } from "react-indexed-db-hook"; import { useAuth } from "@/Contexts/AuthContext"; import { useDebounce } from "@/Hooks/useDebounce"; +import { useIsFirstRender } from "@/Hooks/useIsFirstRender"; interface DraftNft { nftId: number; @@ -39,20 +40,25 @@ export const useGalleryDrafts = (givenDraftId?: number) => { const [title, setTitle] = useState(""); const [debouncedValue] = useDebounce(title, 400); + const isFirstRender = useIsFirstRender(); + // populate `draft` state if `draftId` is present useEffect(() => { - if (draft.id === null && givenDraftId != null) { - const getDraft = async (): Promise => { - const draft: GalleryDraft = await database.getByID(givenDraftId); + if (givenDraftId === undefined) return; + const getDraft = async (): Promise => { + const draft: GalleryDraft = await database.getByID(givenDraftId); + + if (draft.walletAddress === wallet?.address) { setDraft(draft); - }; + } + }; - void getDraft(); - } + void getDraft(); }, []); // persist debounced title useEffect(() => { + if (isFirstRender) return; setDraft({ ...draft, title: debouncedValue }); void saveDraft(); }, [debouncedValue]); From 1f9281788ec0f8cff96c289f6240580f42efedb8 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 20:54:13 +0400 Subject: [PATCH 05/35] wip --- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 36 +++++++++++-------- .../Pages/Galleries/hooks/useGalleryForm.ts | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index ebcd3a4ae..3394cf976 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -42,9 +42,10 @@ export const useGalleryDrafts = (givenDraftId?: number) => { const isFirstRender = useIsFirstRender(); - // populate `draft` state if `draftId` is present + // populate `draft` state if `givenDraftId` is present useEffect(() => { if (givenDraftId === undefined) return; + console.log("getting the draft", givenDraftId); const getDraft = async (): Promise => { const draft: GalleryDraft = await database.getByID(givenDraftId); @@ -59,17 +60,23 @@ export const useGalleryDrafts = (givenDraftId?: number) => { // persist debounced title useEffect(() => { if (isFirstRender) return; - setDraft({ ...draft, title: debouncedValue }); - void saveDraft(); + + const updatedDraft = { ...draft, title: debouncedValue }; + + setDraft(updatedDraft); + void saveDraft(updatedDraft); }, [debouncedValue]); - const saveDraft = async (): Promise => { + const saveDraft = async (draft: GalleryDraft): Promise => { if (isSaving) return; setIsSaving(true); if (draft.id === null) { - const id = await database.add(draft); + const draftToCreate: Partial = { ...draft }; + delete draftToCreate.id; + + const id = await database.add(draftToCreate); setDraft({ ...draft, id }); } else { await database.update(draft); @@ -78,32 +85,31 @@ export const useGalleryDrafts = (givenDraftId?: number) => { setIsSaving(false); }; - const setDraftTitle = (title: string): void => { - setTitle(title); - }; - const setDraftCover = async (image: ArrayBuffer | null): Promise => { - setDraft({ ...draft, cover: image }); - await saveDraft(); + const updatedDraft = { ...draft, cover: image }; + setDraft(updatedDraft); + await saveDraft(updatedDraft); }; const setDraftNfts = async (nfts: App.Data.Gallery.GalleryNftData[]): Promise => { - setDraft({ + const updatedDraft = { ...draft, nfts: nfts.map((nft) => ({ nftId: nft.id, image: nft.images.large ?? "", collectionSlug: nft.collectionSlug, })), - }); + }; + + setDraft(updatedDraft); - await saveDraft(); + await saveDraft(updatedDraft); }; return { draft, setDraftCover, setDraftNfts, - setDraftTitle, + setDraftTitle: setTitle, }; }; diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index 1e3fa6254..bd5763ca5 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -36,7 +36,7 @@ export const useGalleryForm = ({ const { draftId: givenDraftId } = getQueryParameters(); const { setDraftCover, setDraftTitle, setDraftNfts, draft } = useGalleryDrafts( - givenDraftId !== "" ? Number(givenDraftId) : undefined, + isTruthy(givenDraftId) ? Number(givenDraftId) : undefined, ); useEffect(() => { From 8cc74186bc1a6903983cd3f8a7bcff9e153a7ec0 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 21:14:16 +0400 Subject: [PATCH 06/35] wip --- resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 3394cf976..476fe8be7 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -67,7 +67,10 @@ export const useGalleryDrafts = (givenDraftId?: number) => { void saveDraft(updatedDraft); }, [debouncedValue]); + console.log("rendered", draft); + const saveDraft = async (draft: GalleryDraft): Promise => { + console.log("saving draft"); if (isSaving) return; setIsSaving(true); From b03b234046bf708f386c55fc37ba593519213249 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Mon, 30 Oct 2023 21:38:05 +0400 Subject: [PATCH 07/35] wip --- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 476fe8be7..4ba4c3013 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -35,6 +35,7 @@ export const useGalleryDrafts = (givenDraftId?: number) => { walletAddress: wallet?.address, }); + const [save, setSave] = useState(false); const [isSaving, setIsSaving] = useState(false); const [title, setTitle] = useState(""); @@ -45,7 +46,6 @@ export const useGalleryDrafts = (givenDraftId?: number) => { // populate `draft` state if `givenDraftId` is present useEffect(() => { if (givenDraftId === undefined) return; - console.log("getting the draft", givenDraftId); const getDraft = async (): Promise => { const draft: GalleryDraft = await database.getByID(givenDraftId); @@ -57,22 +57,21 @@ export const useGalleryDrafts = (givenDraftId?: number) => { void getDraft(); }, []); - // persist debounced title + // handle debounced title useEffect(() => { if (isFirstRender) return; - const updatedDraft = { ...draft, title: debouncedValue }; - - setDraft(updatedDraft); - void saveDraft(updatedDraft); + setDraft({ ...draft, title: debouncedValue }); + setSave(true); }, [debouncedValue]); - console.log("rendered", draft); + useEffect(() => { + if (!save || isSaving) return; - const saveDraft = async (draft: GalleryDraft): Promise => { - console.log("saving draft"); - if (isSaving) return; + void saveDraft(); + }, [save]); + const saveDraft = async (): Promise => { setIsSaving(true); if (draft.id === null) { @@ -85,28 +84,25 @@ export const useGalleryDrafts = (givenDraftId?: number) => { await database.update(draft); } + setSave(false); setIsSaving(false); }; - const setDraftCover = async (image: ArrayBuffer | null): Promise => { - const updatedDraft = { ...draft, cover: image }; - setDraft(updatedDraft); - await saveDraft(updatedDraft); + const setDraftCover = (image: ArrayBuffer | null): void => { + setDraft({ ...draft, cover: image }); + setSave(true); }; - const setDraftNfts = async (nfts: App.Data.Gallery.GalleryNftData[]): Promise => { - const updatedDraft = { + const setDraftNfts = (nfts: App.Data.Gallery.GalleryNftData[]): void => { + setDraft({ ...draft, nfts: nfts.map((nft) => ({ nftId: nft.id, image: nft.images.large ?? "", collectionSlug: nft.collectionSlug, })), - }; - - setDraft(updatedDraft); - - await saveDraft(updatedDraft); + }); + setSave(true); }; return { From 3bcd7fc1dd78241e51f29392c9e49cf457996385 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Tue, 31 Oct 2023 15:08:19 +0400 Subject: [PATCH 08/35] wip --- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 4ba4c3013..3daf1a170 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -25,7 +25,17 @@ const initialGalleryDraft: GalleryDraft = { id: null, }; -export const useGalleryDrafts = (givenDraftId?: number) => { +const MAX_DRAFT_LIMIT_PER_WALLET = 6; + +interface GalleryDraftsState { + reachedLimit: boolean; + draft: GalleryDraft; + setDraftCover: (image: ArrayBuffer | null) => void; + setDraftNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; + setDraftTitle: (title: string) => void; +} + +export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const { wallet } = useAuth(); const database = useIndexedDB("gallery-drafts"); @@ -43,6 +53,8 @@ export const useGalleryDrafts = (givenDraftId?: number) => { const isFirstRender = useIsFirstRender(); + const [reachedLimit, setReachedLimit] = useState(false); + // populate `draft` state if `givenDraftId` is present useEffect(() => { if (givenDraftId === undefined) return; @@ -66,7 +78,7 @@ export const useGalleryDrafts = (givenDraftId?: number) => { }, [debouncedValue]); useEffect(() => { - if (!save || isSaving) return; + if (!save || isSaving || reachedLimit) return; void saveDraft(); }, [save]); @@ -75,6 +87,13 @@ export const useGalleryDrafts = (givenDraftId?: number) => { setIsSaving(true); if (draft.id === null) { + const walletDrafts = await getWalletDrafts(); + + if (walletDrafts.length >= MAX_DRAFT_LIMIT_PER_WALLET) { + setReachedLimit(true); + return; + } + const draftToCreate: Partial = { ...draft }; delete draftToCreate.id; @@ -88,6 +107,12 @@ export const useGalleryDrafts = (givenDraftId?: number) => { setIsSaving(false); }; + const getWalletDrafts = async (): Promise => { + const allDrafts: GalleryDraft[] = await database.getAll(); + + return allDrafts.filter((draft) => draft.walletAddress === wallet?.address); + }; + const setDraftCover = (image: ArrayBuffer | null): void => { setDraft({ ...draft, cover: image }); setSave(true); @@ -106,6 +131,7 @@ export const useGalleryDrafts = (givenDraftId?: number) => { }; return { + reachedLimit, draft, setDraftCover, setDraftNfts, From 4a49115d478ff8279ec5c9a55c2437a0faa599ea Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Tue, 31 Oct 2023 15:10:44 +0400 Subject: [PATCH 09/35] wip --- .../js/Pages/Galleries/MyGalleries/Create.tsx | 12 +++------- .../Pages/Galleries/hooks/useGalleryForm.ts | 23 +------------------ 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index aef36be03..2d12ef6a6 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -57,10 +57,9 @@ const Create = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [busy, setBusy] = useState(false); - const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing, setDraftCover } = - useGalleryForm({ - gallery, - }); + const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing } = useGalleryForm({ + gallery, + }); const totalValue = 0; @@ -199,13 +198,8 @@ const Create = ({ setGalleryCoverImageUrl(imageDataURI); if (blob === undefined) { setData("coverImage", null); - void setDraftCover(null); } else { setData("coverImage", new File([blob], blob.name, { type: blob.type })); - // eslint ignore - void blob.arrayBuffer().then((buf) => { - void setDraftCover(buf); - }); } setIsGalleryFormSliderOpen(false); }} diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index bd5763ca5..402647db9 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -1,11 +1,8 @@ import { useForm } from "@inertiajs/react"; -import { type FormEvent, useEffect, useState } from "react"; +import { type FormEvent, useState } from "react"; import { useTranslation } from "react-i18next"; import { useToasts } from "@/Hooks/useToasts"; -import { useGalleryDrafts } from "@/Pages/Galleries/hooks/useGalleryDrafts"; -import { getQueryParameters } from "@/Utils/get-query-parameters"; import { isTruthy } from "@/Utils/is-truthy"; -import { replaceUrlQuery } from "@/Utils/replace-url-query"; interface UseGalleryFormProperties extends Record { id: number | null; @@ -27,22 +24,11 @@ export const useGalleryForm = ({ errors: Partial>; updateSelectedNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; processing: boolean; - setDraftCover: (image: ArrayBuffer | null) => Promise; } => { const { t } = useTranslation(); const [selectedNfts, setSelectedNfts] = useState([]); const { showToast } = useToasts(); - const { draftId: givenDraftId } = getQueryParameters(); - - const { setDraftCover, setDraftTitle, setDraftNfts, draft } = useGalleryDrafts( - isTruthy(givenDraftId) ? Number(givenDraftId) : undefined, - ); - - useEffect(() => { - draft.id != null && replaceUrlQuery({ draftId: draft.id.toString() }); - }, [draft.id]); - const { data, setData, post, processing, errors, ...form } = useForm({ id: gallery?.id ?? null, name: gallery?.name ?? "", @@ -113,8 +99,6 @@ export const useGalleryForm = ({ "nfts", nfts.map((nft) => nft.id), ); - - void setDraftNfts(nfts); }; return { @@ -124,17 +108,12 @@ export const useGalleryForm = ({ submit, errors, processing, - setDraftCover, setData: (field, value) => { setData(field, value); if (field === "name" && validateName(field)) { form.setError("name", ""); } - - if (field === "name") { - setDraftTitle(typeof value === "string" ? value : ""); - } }, }; }; From b0aef6f6ee1df39eeca9aa80870047da483abd7f Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Tue, 31 Oct 2023 15:43:05 +0400 Subject: [PATCH 10/35] wip --- resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts | 8 +++++--- resources/js/databaseConfig.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 3daf1a170..b7b17d099 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -13,6 +13,7 @@ interface DraftNft { interface GalleryDraft { title: string; cover: ArrayBuffer | null; + coverType: string | null; nfts: DraftNft[]; walletAddress?: string; id: number | null; @@ -21,6 +22,7 @@ interface GalleryDraft { const initialGalleryDraft: GalleryDraft = { title: "", cover: null, + coverType: null, nfts: [], id: null, }; @@ -30,7 +32,7 @@ const MAX_DRAFT_LIMIT_PER_WALLET = 6; interface GalleryDraftsState { reachedLimit: boolean; draft: GalleryDraft; - setDraftCover: (image: ArrayBuffer | null) => void; + setDraftCover: (image: ArrayBuffer | null, type: string | null) => void; setDraftNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; setDraftTitle: (title: string) => void; } @@ -113,8 +115,8 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { return allDrafts.filter((draft) => draft.walletAddress === wallet?.address); }; - const setDraftCover = (image: ArrayBuffer | null): void => { - setDraft({ ...draft, cover: image }); + const setDraftCover = (image: ArrayBuffer | null, type: string | null): void => { + setDraft({ ...draft, cover: image, coverType: type }); setSave(true); }; diff --git a/resources/js/databaseConfig.ts b/resources/js/databaseConfig.ts index 2a69fbfbd..cd14e6ea3 100644 --- a/resources/js/databaseConfig.ts +++ b/resources/js/databaseConfig.ts @@ -9,6 +9,7 @@ export const databaseConfig = { { name: "walletAddress", keypath: "walletAddress", options: { unique: false } }, { name: "title", keypath: "title", options: { unique: false } }, { name: "cover", keypath: "cover", options: { unique: false } }, + { name: "coverType", keypath: "coverType", options: { unique: false } }, { name: "nfts", keypath: "nfts", options: { unique: false } }, ], }, From 63bc5a96afe244b151693f80a40b1745a9816160 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Tue, 31 Oct 2023 19:16:40 +0400 Subject: [PATCH 11/35] wip --- .../js/Pages/Galleries/MyGalleries/Create.tsx | 24 ++++++++++++++++++- .../Pages/Galleries/hooks/useGalleryForm.ts | 4 ++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 2d12ef6a6..b8daacfc2 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -1,7 +1,7 @@ import { type PageProps, type VisitOptions } from "@inertiajs/core"; import { Head, router, usePage } from "@inertiajs/react"; import uniqBy from "lodash/uniqBy"; -import { type FormEvent, type MouseEvent, useCallback, useMemo, useState } from "react"; +import { type FormEvent, type MouseEvent, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { ConfirmDeletionDialog } from "@/Components/ConfirmDeletionDialog"; import { FeaturedCollectionsBanner } from "@/Components/FeaturedCollectionsBanner"; @@ -17,9 +17,12 @@ import { NoNftsOverlay } from "@/Components/Layout/NoNftsOverlay"; import { useMetaMaskContext } from "@/Contexts/MetaMaskContext"; import { useAuthorizedAction } from "@/Hooks/useAuthorizedAction"; import { GalleryNameInput } from "@/Pages/Galleries/Components/GalleryNameInput"; +import { useGalleryDrafts } from "@/Pages/Galleries/hooks/useGalleryDrafts"; import { useGalleryForm } from "@/Pages/Galleries/hooks/useGalleryForm"; import { assertUser, assertWallet } from "@/Utils/assertions"; +import { getQueryParameters } from "@/Utils/get-query-parameters"; import { isTruthy } from "@/Utils/is-truthy"; +import { replaceUrlQuery } from "@/Utils/replace-url-query"; interface Properties { auth: PageProps["auth"]; @@ -57,8 +60,21 @@ const Create = ({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [busy, setBusy] = useState(false); + const { draftId } = getQueryParameters(); + + const { setDraftCover, setDraftNfts, setDraftTitle, draft } = useGalleryDrafts( + isTruthy(draftId) ? Number(draftId) : undefined, + ); + + useEffect(() => { + if (draft.id !== null) { + replaceUrlQuery({ draftId: draft.id.toString() }); + } + }, [draft.id]); + const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing } = useGalleryForm({ gallery, + setDraftNfts, }); const totalValue = 0; @@ -117,6 +133,7 @@ const Create = ({ error={errors.name} name={data.name} onChange={(name) => { + setDraftTitle(name); setData("name", name); }} /> @@ -198,8 +215,13 @@ const Create = ({ setGalleryCoverImageUrl(imageDataURI); if (blob === undefined) { setData("coverImage", null); + setDraftCover(null, null); } else { setData("coverImage", new File([blob], blob.name, { type: blob.type })); + // eslint-disable-next-line promise/prefer-await-to-then + void blob.arrayBuffer().then((buf) => { + setDraftCover(buf, blob.type); + }); } setIsGalleryFormSliderOpen(false); }} diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index 402647db9..206131755 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -13,8 +13,10 @@ interface UseGalleryFormProperties extends Record { export const useGalleryForm = ({ gallery, + setDraftNfts, }: { gallery?: App.Data.Gallery.GalleryData; + setDraftNfts?: (nfts: App.Data.Gallery.GalleryNftData[]) => void; }): { selectedNfts: App.Data.Gallery.GalleryNftData[]; gallery?: App.Data.Gallery.GalleryData; @@ -99,6 +101,8 @@ export const useGalleryForm = ({ "nfts", nfts.map((nft) => nft.id), ); + + setDraftNfts?.(nfts); }; return { From df01afd205eb18d6bdf1be354fc10054b5a58810 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 20:33:43 +0400 Subject: [PATCH 12/35] wip --- .../Galleries/hooks/useGalleryDrafts.test.ts | 175 ++++++++++++++++++ .../Pages/Galleries/hooks/useGalleryDrafts.ts | 44 ++--- 2 files changed, 191 insertions(+), 28 deletions(-) create mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts new file mode 100644 index 000000000..7a3e3ecba --- /dev/null +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -0,0 +1,175 @@ +import { expect, type SpyInstance } from "vitest"; +import { useGalleryDrafts } from "./useGalleryDrafts"; +import * as AuthContextMock from "@/Contexts/AuthContext"; + +import GalleryNftDataFactory from "@/Tests/Factories/Gallery/GalleryNftDataFactory"; +import { act, renderHook, waitFor } from "@/Tests/testing-library"; + +let useAuthSpy: SpyInstance; +vi.mock("@/Contexts/AuthContext", () => ({ + useAuth: () => ({ wallet: { address: "mockedWalletAddress" } }), +})); + +const defaultGalleryDraft = { + id: 1, + walletAddress: "mockedAddress", + nfts: [], + title: "", + cover: null, + coverTye: null, +}; + +const indexedDBMocks = { + add: vi.fn(), + getAll: vi.fn().mockResolvedValue([]), + update: vi.fn(), + deleteRecord: vi.fn(), + openCursor: vi.fn(), + getByIndex: vi.fn(), + clear: vi.fn(), + getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), +}; + +const mocks = vi.hoisted(() => ({ + useIndexedDB: () => indexedDBMocks, +})); + +vi.mock("react-indexed-db-hook", () => ({ + useIndexedDB: mocks.useIndexedDB, +})); + +describe("useGalleryDrafts custom hook", () => { + beforeAll(() => { + useAuthSpy = vi.spyOn(AuthContextMock, "useAuth").mockReturnValue({ + user: null, + wallet: { + address: "mockedAddress", + domain: null, + totalUsd: 1, + totalBalanceInCurrency: "1", + totalTokens: 1, + collectionCount: 1, + galleryCount: 1, + timestamps: { + tokens_fetched_at: null, + native_balances_fetched_at: null, + }, + isRefreshingCollections: false, + canRefreshCollections: false, + avatar: { + small: null, + default: null, + small2x: null, + }, + }, + authenticated: false, + signed: false, + logout: vi.fn(), + setAuthData: vi.fn(), + }); + }); + + afterAll(() => { + useAuthSpy.mockRestore(); + }); + + it("should populate draft when givenDraftId is provided", async () => { + const givenDraftId = 1; + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(givenDraftId); + }); + }); + + it("should keep draft in initial state when givenDraftId is irrelevant", async () => { + mocks.useIndexedDB().getByID.mockResolvedValue({ + ...defaultGalleryDraft, + walletAddress: "unrelatedAddress", + }); + + const givenDraftId = 1; + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(null); + }); + }); + + it("should try to create a new row if draft hasn't been created yet", async () => { + mocks.useIndexedDB().add.mockResolvedValue(2); + + const { result } = renderHook(() => useGalleryDrafts()); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(result.current.draft.id).toBe(2); + }); + }); + + it("should try to update the row if draft is present", async () => { + const givenDraftId = 2; + const updateMock = vi.fn(); + + mocks.useIndexedDB().getByID.mockResolvedValue({ ...defaultGalleryDraft, id: givenDraftId }); + mocks.useIndexedDB().update.mockImplementation(updateMock); + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(givenDraftId); + }); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(updateMock).toHaveBeenCalledOnce(); + }); + }); + + it("should update the title", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(result.current.draft.title).toBe("hello"); + }); + }); + + it("should update the nfts", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + const nft = new GalleryNftDataFactory().create(); + + act(() => { + result.current.setDraftNfts([nft]); + }); + + await waitFor(() => { + expect(result.current.draft.nfts.length).toBe(1); + }); + }); + + it("should update the cover", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + act(() => { + result.current.setDraftCover(new ArrayBuffer(8), "png"); + }); + + await waitFor(() => { + expect(result.current.draft.cover).not.toBeNull(); + expect(result.current.draft.coverType).toBe("png"); + }); + }); +}); diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index b7b17d099..01c06de05 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -1,9 +1,8 @@ import { useEffect, useState } from "react"; import { useIndexedDB } from "react-indexed-db-hook"; import { useAuth } from "@/Contexts/AuthContext"; -import { useDebounce } from "@/Hooks/useDebounce"; -import { useIsFirstRender } from "@/Hooks/useIsFirstRender"; +const MAX_DRAFT_LIMIT_PER_WALLET = 6; interface DraftNft { nftId: number; image: string; @@ -19,16 +18,6 @@ interface GalleryDraft { id: number | null; } -const initialGalleryDraft: GalleryDraft = { - title: "", - cover: null, - coverType: null, - nfts: [], - id: null, -}; - -const MAX_DRAFT_LIMIT_PER_WALLET = 6; - interface GalleryDraftsState { reachedLimit: boolean; draft: GalleryDraft; @@ -37,6 +26,14 @@ interface GalleryDraftsState { setDraftTitle: (title: string) => void; } +const initialGalleryDraft: GalleryDraft = { + title: "", + cover: null, + coverType: null, + nfts: [], + id: null, +}; + export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const { wallet } = useAuth(); @@ -50,11 +47,6 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const [save, setSave] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [title, setTitle] = useState(""); - const [debouncedValue] = useDebounce(title, 400); - - const isFirstRender = useIsFirstRender(); - const [reachedLimit, setReachedLimit] = useState(false); // populate `draft` state if `givenDraftId` is present @@ -62,22 +54,13 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { if (givenDraftId === undefined) return; const getDraft = async (): Promise => { const draft: GalleryDraft = await database.getByID(givenDraftId); - if (draft.walletAddress === wallet?.address) { setDraft(draft); } }; void getDraft(); - }, []); - - // handle debounced title - useEffect(() => { - if (isFirstRender) return; - - setDraft({ ...draft, title: debouncedValue }); - setSave(true); - }, [debouncedValue]); + }, [givenDraftId, wallet?.address]); useEffect(() => { if (!save || isSaving || reachedLimit) return; @@ -132,11 +115,16 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { setSave(true); }; + const setDraftTitle = (title: string): void => { + setDraft({ ...draft, title }); + setSave(true); + }; + return { reachedLimit, draft, setDraftCover, setDraftNfts, - setDraftTitle: setTitle, + setDraftTitle, }; }; From 69e3f557c0a366c95afe449a1991b05a7044809d Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 20:41:28 +0400 Subject: [PATCH 13/35] wip --- .../Galleries/hooks/useGalleryDrafts.test.ts | 175 ++++++++++++++++++ .../Pages/Galleries/hooks/useGalleryDrafts.ts | 44 ++--- 2 files changed, 191 insertions(+), 28 deletions(-) create mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts new file mode 100644 index 000000000..7a3e3ecba --- /dev/null +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -0,0 +1,175 @@ +import { expect, type SpyInstance } from "vitest"; +import { useGalleryDrafts } from "./useGalleryDrafts"; +import * as AuthContextMock from "@/Contexts/AuthContext"; + +import GalleryNftDataFactory from "@/Tests/Factories/Gallery/GalleryNftDataFactory"; +import { act, renderHook, waitFor } from "@/Tests/testing-library"; + +let useAuthSpy: SpyInstance; +vi.mock("@/Contexts/AuthContext", () => ({ + useAuth: () => ({ wallet: { address: "mockedWalletAddress" } }), +})); + +const defaultGalleryDraft = { + id: 1, + walletAddress: "mockedAddress", + nfts: [], + title: "", + cover: null, + coverTye: null, +}; + +const indexedDBMocks = { + add: vi.fn(), + getAll: vi.fn().mockResolvedValue([]), + update: vi.fn(), + deleteRecord: vi.fn(), + openCursor: vi.fn(), + getByIndex: vi.fn(), + clear: vi.fn(), + getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), +}; + +const mocks = vi.hoisted(() => ({ + useIndexedDB: () => indexedDBMocks, +})); + +vi.mock("react-indexed-db-hook", () => ({ + useIndexedDB: mocks.useIndexedDB, +})); + +describe("useGalleryDrafts custom hook", () => { + beforeAll(() => { + useAuthSpy = vi.spyOn(AuthContextMock, "useAuth").mockReturnValue({ + user: null, + wallet: { + address: "mockedAddress", + domain: null, + totalUsd: 1, + totalBalanceInCurrency: "1", + totalTokens: 1, + collectionCount: 1, + galleryCount: 1, + timestamps: { + tokens_fetched_at: null, + native_balances_fetched_at: null, + }, + isRefreshingCollections: false, + canRefreshCollections: false, + avatar: { + small: null, + default: null, + small2x: null, + }, + }, + authenticated: false, + signed: false, + logout: vi.fn(), + setAuthData: vi.fn(), + }); + }); + + afterAll(() => { + useAuthSpy.mockRestore(); + }); + + it("should populate draft when givenDraftId is provided", async () => { + const givenDraftId = 1; + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(givenDraftId); + }); + }); + + it("should keep draft in initial state when givenDraftId is irrelevant", async () => { + mocks.useIndexedDB().getByID.mockResolvedValue({ + ...defaultGalleryDraft, + walletAddress: "unrelatedAddress", + }); + + const givenDraftId = 1; + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(null); + }); + }); + + it("should try to create a new row if draft hasn't been created yet", async () => { + mocks.useIndexedDB().add.mockResolvedValue(2); + + const { result } = renderHook(() => useGalleryDrafts()); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(result.current.draft.id).toBe(2); + }); + }); + + it("should try to update the row if draft is present", async () => { + const givenDraftId = 2; + const updateMock = vi.fn(); + + mocks.useIndexedDB().getByID.mockResolvedValue({ ...defaultGalleryDraft, id: givenDraftId }); + mocks.useIndexedDB().update.mockImplementation(updateMock); + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(givenDraftId); + }); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(updateMock).toHaveBeenCalledOnce(); + }); + }); + + it("should update the title", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(result.current.draft.title).toBe("hello"); + }); + }); + + it("should update the nfts", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + const nft = new GalleryNftDataFactory().create(); + + act(() => { + result.current.setDraftNfts([nft]); + }); + + await waitFor(() => { + expect(result.current.draft.nfts.length).toBe(1); + }); + }); + + it("should update the cover", async () => { + const { result } = renderHook(() => useGalleryDrafts(1)); + + act(() => { + result.current.setDraftCover(new ArrayBuffer(8), "png"); + }); + + await waitFor(() => { + expect(result.current.draft.cover).not.toBeNull(); + expect(result.current.draft.coverType).toBe("png"); + }); + }); +}); diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index b7b17d099..01c06de05 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -1,9 +1,8 @@ import { useEffect, useState } from "react"; import { useIndexedDB } from "react-indexed-db-hook"; import { useAuth } from "@/Contexts/AuthContext"; -import { useDebounce } from "@/Hooks/useDebounce"; -import { useIsFirstRender } from "@/Hooks/useIsFirstRender"; +const MAX_DRAFT_LIMIT_PER_WALLET = 6; interface DraftNft { nftId: number; image: string; @@ -19,16 +18,6 @@ interface GalleryDraft { id: number | null; } -const initialGalleryDraft: GalleryDraft = { - title: "", - cover: null, - coverType: null, - nfts: [], - id: null, -}; - -const MAX_DRAFT_LIMIT_PER_WALLET = 6; - interface GalleryDraftsState { reachedLimit: boolean; draft: GalleryDraft; @@ -37,6 +26,14 @@ interface GalleryDraftsState { setDraftTitle: (title: string) => void; } +const initialGalleryDraft: GalleryDraft = { + title: "", + cover: null, + coverType: null, + nfts: [], + id: null, +}; + export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const { wallet } = useAuth(); @@ -50,11 +47,6 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const [save, setSave] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [title, setTitle] = useState(""); - const [debouncedValue] = useDebounce(title, 400); - - const isFirstRender = useIsFirstRender(); - const [reachedLimit, setReachedLimit] = useState(false); // populate `draft` state if `givenDraftId` is present @@ -62,22 +54,13 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { if (givenDraftId === undefined) return; const getDraft = async (): Promise => { const draft: GalleryDraft = await database.getByID(givenDraftId); - if (draft.walletAddress === wallet?.address) { setDraft(draft); } }; void getDraft(); - }, []); - - // handle debounced title - useEffect(() => { - if (isFirstRender) return; - - setDraft({ ...draft, title: debouncedValue }); - setSave(true); - }, [debouncedValue]); + }, [givenDraftId, wallet?.address]); useEffect(() => { if (!save || isSaving || reachedLimit) return; @@ -132,11 +115,16 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { setSave(true); }; + const setDraftTitle = (title: string): void => { + setDraft({ ...draft, title }); + setSave(true); + }; + return { reachedLimit, draft, setDraftCover, setDraftNfts, - setDraftTitle: setTitle, + setDraftTitle, }; }; From 228ea5961c0cd99fbfa43d1c0491bb0257a45271 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 20:51:09 +0400 Subject: [PATCH 14/35] wip --- .../Galleries/hooks/useGalleryDrafts.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts index 7a3e3ecba..8b0aab1f5 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -172,4 +172,22 @@ describe("useGalleryDrafts custom hook", () => { expect(result.current.draft.coverType).toBe("png"); }); }); + + it("should not add new draft if reached to the limit", async () => { + const addMock = vi.fn(); + + mocks.useIndexedDB().getAll.mockReturnValue(Array.from({ length: 6 }).fill({ walletAddress: "mockedAddress" })); + mocks.useIndexedDB().add.mockImplementation(addMock); + + const { result } = renderHook(() => useGalleryDrafts()); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(addMock).not.toHaveBeenCalled(); + expect(result.current.reachedLimit).toBe(true); + }); + }); }); From 79f5d9b90bf08df7247f06be33cdad3d82fad768 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 20:51:31 +0400 Subject: [PATCH 15/35] wip --- .../js/{Pages/Galleries/hooks => Hooks}/useGalleryDrafts.test.ts | 0 resources/js/{Pages/Galleries/hooks => Hooks}/useGalleryDrafts.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename resources/js/{Pages/Galleries/hooks => Hooks}/useGalleryDrafts.test.ts (100%) rename resources/js/{Pages/Galleries/hooks => Hooks}/useGalleryDrafts.ts (100%) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Hooks/useGalleryDrafts.test.ts similarity index 100% rename from resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts rename to resources/js/Hooks/useGalleryDrafts.test.ts diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Hooks/useGalleryDrafts.ts similarity index 100% rename from resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts rename to resources/js/Hooks/useGalleryDrafts.ts From 19b5d549532ba88c28caddb88f154e95cfafbe61 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 20:53:29 +0400 Subject: [PATCH 16/35] wip --- resources/js/Hooks/useGalleryDrafts.test.ts | 24 ++++++++++----------- resources/js/Hooks/useGalleryDrafts.ts | 1 + 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/resources/js/Hooks/useGalleryDrafts.test.ts b/resources/js/Hooks/useGalleryDrafts.test.ts index 8b0aab1f5..a72e33593 100644 --- a/resources/js/Hooks/useGalleryDrafts.test.ts +++ b/resources/js/Hooks/useGalleryDrafts.test.ts @@ -1,11 +1,11 @@ import { expect, type SpyInstance } from "vitest"; import { useGalleryDrafts } from "./useGalleryDrafts"; import * as AuthContextMock from "@/Contexts/AuthContext"; - import GalleryNftDataFactory from "@/Tests/Factories/Gallery/GalleryNftDataFactory"; import { act, renderHook, waitFor } from "@/Tests/testing-library"; let useAuthSpy: SpyInstance; + vi.mock("@/Contexts/AuthContext", () => ({ useAuth: () => ({ wallet: { address: "mockedWalletAddress" } }), })); @@ -19,19 +19,17 @@ const defaultGalleryDraft = { coverTye: null, }; -const indexedDBMocks = { - add: vi.fn(), - getAll: vi.fn().mockResolvedValue([]), - update: vi.fn(), - deleteRecord: vi.fn(), - openCursor: vi.fn(), - getByIndex: vi.fn(), - clear: vi.fn(), - getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), -}; - const mocks = vi.hoisted(() => ({ - useIndexedDB: () => indexedDBMocks, + useIndexedDB: () => ({ + add: vi.fn(), + getAll: vi.fn().mockResolvedValue([]), + update: vi.fn(), + deleteRecord: vi.fn(), + openCursor: vi.fn(), + getByIndex: vi.fn(), + clear: vi.fn(), + getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), + }), })); vi.mock("react-indexed-db-hook", () => ({ diff --git a/resources/js/Hooks/useGalleryDrafts.ts b/resources/js/Hooks/useGalleryDrafts.ts index 01c06de05..179b5cff4 100644 --- a/resources/js/Hooks/useGalleryDrafts.ts +++ b/resources/js/Hooks/useGalleryDrafts.ts @@ -3,6 +3,7 @@ import { useIndexedDB } from "react-indexed-db-hook"; import { useAuth } from "@/Contexts/AuthContext"; const MAX_DRAFT_LIMIT_PER_WALLET = 6; + interface DraftNft { nftId: number; image: string; From 05ed6d4fbe7f6818aae3c23d72d4d5a00858642e Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 21:01:27 +0400 Subject: [PATCH 17/35] wip --- .../GalleryNameInput/GalleryNameInput.tsx | 5 + .../js/Pages/Galleries/MyGalleries/Create.tsx | 7 +- .../Galleries/hooks/useGalleryDrafts.test.ts | 175 ------------------ 3 files changed, 10 insertions(+), 177 deletions(-) delete mode 100644 resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts diff --git a/resources/js/Pages/Galleries/Components/GalleryNameInput/GalleryNameInput.tsx b/resources/js/Pages/Galleries/Components/GalleryNameInput/GalleryNameInput.tsx index e281a5ae4..589ebebcd 100644 --- a/resources/js/Pages/Galleries/Components/GalleryNameInput/GalleryNameInput.tsx +++ b/resources/js/Pages/Galleries/Components/GalleryNameInput/GalleryNameInput.tsx @@ -8,12 +8,14 @@ import { isTruthy } from "@/Utils/is-truthy"; export const GalleryNameInput = ({ name, onChange, + onBlur, error, maxLength = 50, }: { maxLength?: number; name: string; onChange?: (name: string) => void; + onBlur?: () => void; error?: string; }): JSX.Element => { const { t } = useTranslation(); @@ -48,6 +50,9 @@ export const GalleryNameInput = ({ type="text" maxLength={maxLength + 1} value={name} + onBlur={() => { + onBlur?.(); + }} onChange={(event) => { onChange?.(event.target.value); }} diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index b8daacfc2..f8ea5563f 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -16,8 +16,8 @@ import { LayoutWrapper } from "@/Components/Layout/LayoutWrapper"; import { NoNftsOverlay } from "@/Components/Layout/NoNftsOverlay"; import { useMetaMaskContext } from "@/Contexts/MetaMaskContext"; import { useAuthorizedAction } from "@/Hooks/useAuthorizedAction"; +import { useGalleryDrafts } from "@/Hooks/useGalleryDrafts"; import { GalleryNameInput } from "@/Pages/Galleries/Components/GalleryNameInput"; -import { useGalleryDrafts } from "@/Pages/Galleries/hooks/useGalleryDrafts"; import { useGalleryForm } from "@/Pages/Galleries/hooks/useGalleryForm"; import { assertUser, assertWallet } from "@/Utils/assertions"; import { getQueryParameters } from "@/Utils/get-query-parameters"; @@ -133,9 +133,11 @@ const Create = ({ error={errors.name} name={data.name} onChange={(name) => { - setDraftTitle(name); setData("name", name); }} + onBlur={() => { + setDraftTitle(data.name); + }} /> { + setDraftTitle(data.name); router.visit(route("my-galleries")); }} onPublish={publishHandler} diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts deleted file mode 100644 index 7a3e3ecba..000000000 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { expect, type SpyInstance } from "vitest"; -import { useGalleryDrafts } from "./useGalleryDrafts"; -import * as AuthContextMock from "@/Contexts/AuthContext"; - -import GalleryNftDataFactory from "@/Tests/Factories/Gallery/GalleryNftDataFactory"; -import { act, renderHook, waitFor } from "@/Tests/testing-library"; - -let useAuthSpy: SpyInstance; -vi.mock("@/Contexts/AuthContext", () => ({ - useAuth: () => ({ wallet: { address: "mockedWalletAddress" } }), -})); - -const defaultGalleryDraft = { - id: 1, - walletAddress: "mockedAddress", - nfts: [], - title: "", - cover: null, - coverTye: null, -}; - -const indexedDBMocks = { - add: vi.fn(), - getAll: vi.fn().mockResolvedValue([]), - update: vi.fn(), - deleteRecord: vi.fn(), - openCursor: vi.fn(), - getByIndex: vi.fn(), - clear: vi.fn(), - getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), -}; - -const mocks = vi.hoisted(() => ({ - useIndexedDB: () => indexedDBMocks, -})); - -vi.mock("react-indexed-db-hook", () => ({ - useIndexedDB: mocks.useIndexedDB, -})); - -describe("useGalleryDrafts custom hook", () => { - beforeAll(() => { - useAuthSpy = vi.spyOn(AuthContextMock, "useAuth").mockReturnValue({ - user: null, - wallet: { - address: "mockedAddress", - domain: null, - totalUsd: 1, - totalBalanceInCurrency: "1", - totalTokens: 1, - collectionCount: 1, - galleryCount: 1, - timestamps: { - tokens_fetched_at: null, - native_balances_fetched_at: null, - }, - isRefreshingCollections: false, - canRefreshCollections: false, - avatar: { - small: null, - default: null, - small2x: null, - }, - }, - authenticated: false, - signed: false, - logout: vi.fn(), - setAuthData: vi.fn(), - }); - }); - - afterAll(() => { - useAuthSpy.mockRestore(); - }); - - it("should populate draft when givenDraftId is provided", async () => { - const givenDraftId = 1; - - const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); - - await waitFor(() => { - expect(result.current.draft.id).toBe(givenDraftId); - }); - }); - - it("should keep draft in initial state when givenDraftId is irrelevant", async () => { - mocks.useIndexedDB().getByID.mockResolvedValue({ - ...defaultGalleryDraft, - walletAddress: "unrelatedAddress", - }); - - const givenDraftId = 1; - - const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); - - await waitFor(() => { - expect(result.current.draft.id).toBe(null); - }); - }); - - it("should try to create a new row if draft hasn't been created yet", async () => { - mocks.useIndexedDB().add.mockResolvedValue(2); - - const { result } = renderHook(() => useGalleryDrafts()); - - act(() => { - result.current.setDraftTitle("hello"); - }); - - await waitFor(() => { - expect(result.current.draft.id).toBe(2); - }); - }); - - it("should try to update the row if draft is present", async () => { - const givenDraftId = 2; - const updateMock = vi.fn(); - - mocks.useIndexedDB().getByID.mockResolvedValue({ ...defaultGalleryDraft, id: givenDraftId }); - mocks.useIndexedDB().update.mockImplementation(updateMock); - - const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); - - await waitFor(() => { - expect(result.current.draft.id).toBe(givenDraftId); - }); - - act(() => { - result.current.setDraftTitle("hello"); - }); - - await waitFor(() => { - expect(updateMock).toHaveBeenCalledOnce(); - }); - }); - - it("should update the title", async () => { - const { result } = renderHook(() => useGalleryDrafts(1)); - - act(() => { - result.current.setDraftTitle("hello"); - }); - - await waitFor(() => { - expect(result.current.draft.title).toBe("hello"); - }); - }); - - it("should update the nfts", async () => { - const { result } = renderHook(() => useGalleryDrafts(1)); - - const nft = new GalleryNftDataFactory().create(); - - act(() => { - result.current.setDraftNfts([nft]); - }); - - await waitFor(() => { - expect(result.current.draft.nfts.length).toBe(1); - }); - }); - - it("should update the cover", async () => { - const { result } = renderHook(() => useGalleryDrafts(1)); - - act(() => { - result.current.setDraftCover(new ArrayBuffer(8), "png"); - }); - - await waitFor(() => { - expect(result.current.draft.cover).not.toBeNull(); - expect(result.current.draft.coverType).toBe("png"); - }); - }); -}); From 0c373ce352b9ce3dd9c1907a2e6029450c2787a5 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 21:48:22 +0400 Subject: [PATCH 18/35] wip --- .../Galleries/hooks}/useGalleryDrafts.test.ts | 64 +++++++++++++++---- .../Galleries/hooks}/useGalleryDrafts.ts | 13 +++- 2 files changed, 64 insertions(+), 13 deletions(-) rename resources/js/{Hooks => Pages/Galleries/hooks}/useGalleryDrafts.test.ts (77%) rename resources/js/{Hooks => Pages/Galleries/hooks}/useGalleryDrafts.ts (89%) diff --git a/resources/js/Hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts similarity index 77% rename from resources/js/Hooks/useGalleryDrafts.test.ts rename to resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts index a72e33593..a1b63c8bc 100644 --- a/resources/js/Hooks/useGalleryDrafts.test.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -19,24 +19,26 @@ const defaultGalleryDraft = { coverTye: null, }; +const indexedDBMocks = { + add: vi.fn(), + getAll: vi.fn().mockResolvedValue([]), + update: vi.fn(), + deleteRecord: vi.fn(), + openCursor: vi.fn(), + getByIndex: vi.fn(), + clear: vi.fn(), + getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), +}; + const mocks = vi.hoisted(() => ({ - useIndexedDB: () => ({ - add: vi.fn(), - getAll: vi.fn().mockResolvedValue([]), - update: vi.fn(), - deleteRecord: vi.fn(), - openCursor: vi.fn(), - getByIndex: vi.fn(), - clear: vi.fn(), - getByID: vi.fn().mockResolvedValue(defaultGalleryDraft), - }), + useIndexedDB: () => indexedDBMocks, })); vi.mock("react-indexed-db-hook", () => ({ useIndexedDB: mocks.useIndexedDB, })); -describe("useGalleryDrafts custom hook", () => { +describe("useGalleryDrafts", () => { beforeAll(() => { useAuthSpy = vi.spyOn(AuthContextMock, "useAuth").mockReturnValue({ user: null, @@ -188,4 +190,44 @@ describe("useGalleryDrafts custom hook", () => { expect(result.current.reachedLimit).toBe(true); }); }); + + it("should delete the draft if id is present", async () => { + const givenDraftId = 2; + + const deleteMock = vi.fn(); + + mocks.useIndexedDB().getByID.mockResolvedValue({ ...defaultGalleryDraft, id: givenDraftId }); + mocks.useIndexedDB().deleteRecord.mockImplementation(deleteMock); + + const { result } = renderHook(() => useGalleryDrafts(givenDraftId)); + + await waitFor(() => { + expect(result.current.draft.id).toBe(givenDraftId); + }); + + await act(async () => { + await result.current.deleteDraft(); + }); + + await waitFor(() => { + expect(deleteMock).toHaveBeenCalled(); + expect(result.current.reachedLimit).toBe(false); + }); + }); + + it("should not delete the draft if id is not present", async () => { + const deleteMock = vi.fn(); + + mocks.useIndexedDB().deleteRecord.mockImplementation(deleteMock); + + const { result } = renderHook(() => useGalleryDrafts()); + + await act(async () => { + await result.current.deleteDraft(); + }); + + await waitFor(() => { + expect(deleteMock).not.toHaveBeenCalled(); + }); + }); }); diff --git a/resources/js/Hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts similarity index 89% rename from resources/js/Hooks/useGalleryDrafts.ts rename to resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index 179b5cff4..cb4fefcdf 100644 --- a/resources/js/Hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -25,6 +25,7 @@ interface GalleryDraftsState { setDraftCover: (image: ArrayBuffer | null, type: string | null) => void; setDraftNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; setDraftTitle: (title: string) => void; + deleteDraft: () => Promise; } const initialGalleryDraft: GalleryDraft = { @@ -54,8 +55,8 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { useEffect(() => { if (givenDraftId === undefined) return; const getDraft = async (): Promise => { - const draft: GalleryDraft = await database.getByID(givenDraftId); - if (draft.walletAddress === wallet?.address) { + const draft: GalleryDraft | undefined = await database.getByID(givenDraftId); + if (draft !== undefined && draft.walletAddress === wallet?.address) { setDraft(draft); } }; @@ -121,11 +122,19 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { setSave(true); }; + const deleteDraft = async (): Promise => { + if (draft.id === null) return; + await database.deleteRecord(draft.id); + + setReachedLimit(false); + }; + return { reachedLimit, draft, setDraftCover, setDraftNfts, setDraftTitle, + deleteDraft, }; }; From 11be9a8a8d3e162ad4e6431f6390f8e4f9bb6b5e Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Wed, 1 Nov 2023 21:55:10 +0400 Subject: [PATCH 19/35] wip --- resources/js/Pages/Galleries/MyGalleries/Create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index f8ea5563f..97c82ae57 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -16,8 +16,8 @@ import { LayoutWrapper } from "@/Components/Layout/LayoutWrapper"; import { NoNftsOverlay } from "@/Components/Layout/NoNftsOverlay"; import { useMetaMaskContext } from "@/Contexts/MetaMaskContext"; import { useAuthorizedAction } from "@/Hooks/useAuthorizedAction"; -import { useGalleryDrafts } from "@/Hooks/useGalleryDrafts"; import { GalleryNameInput } from "@/Pages/Galleries/Components/GalleryNameInput"; +import { useGalleryDrafts } from "@/Pages/Galleries/hooks/useGalleryDrafts"; import { useGalleryForm } from "@/Pages/Galleries/hooks/useGalleryForm"; import { assertUser, assertWallet } from "@/Utils/assertions"; import { getQueryParameters } from "@/Utils/get-query-parameters"; From 6513069990153d1256cb037b94e793067b75466f Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 00:14:01 +0400 Subject: [PATCH 20/35] wip --- resources/icons/fat-double-check.svg | 5 ++ .../GalleryActionToolbar.tsx | 60 +++++++++++++++---- .../GalleryDraftStatus.tsx | 42 +++++++++++++ .../js/Pages/Galleries/MyGalleries/Create.tsx | 5 +- .../Pages/Galleries/hooks/useGalleryDrafts.ts | 11 ++-- resources/js/icons.tsx | 2 + 6 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 resources/icons/fat-double-check.svg create mode 100644 resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx diff --git a/resources/icons/fat-double-check.svg b/resources/icons/fat-double-check.svg new file mode 100644 index 000000000..43ef2f78c --- /dev/null +++ b/resources/icons/fat-double-check.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx index efb589115..ef8ef1dd5 100644 --- a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx @@ -1,6 +1,8 @@ import { type FormEvent, type MouseEvent, useLayoutEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button, IconButton } from "@/Components/Buttons"; +import { GalleryDraftStatus } from "@/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus"; +import { Icon } from "@/Components/Icon"; import { Img } from "@/Components/Image"; import { isTruthy } from "@/Utils/is-truthy"; @@ -13,6 +15,8 @@ export const GalleryActionToolbar = ({ onCancel, isProcessing = false, showDelete = true, + draftId, + isSavingDraft, }: { onCoverClick?: (event: MouseEvent) => void; onTemplateClick?: (event: MouseEvent) => void; @@ -22,6 +26,8 @@ export const GalleryActionToolbar = ({ galleryCoverUrl?: string; isProcessing?: boolean; showDelete?: boolean; + draftId?: number; + isSavingDraft?: boolean; }): JSX.Element => { const { t } = useTranslation(); const [scrollbarWidth, setScrollbarWidth] = useState(0); @@ -85,17 +91,46 @@ export const GalleryActionToolbar = ({ )} +
+
+ {isTruthy(draftId) && isSavingDraft === false && ( + + )} + + {isSavingDraft === true && ( + + )} +
+
+
- - +
+ +
+ +
+ + +
@@ -158,6 +193,11 @@ export const GalleryActionToolbar = ({
+ + {showDelete && ( { + if (isTruthy(draftId) && isSavingDraft === false) { + return ( +
+ +
+ Draft Saved +
+
+ ); + } + + if (isSavingDraft === true) { + return ( +
+ +
+ Saving to draft +
+
+ ); + } + + return <>; +}; diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 97c82ae57..0d57b3126 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -62,8 +62,9 @@ const Create = ({ const { draftId } = getQueryParameters(); - const { setDraftCover, setDraftNfts, setDraftTitle, draft } = useGalleryDrafts( + const { setDraftCover, setDraftNfts, setDraftTitle, draft, isSaving } = useGalleryDrafts( isTruthy(draftId) ? Number(draftId) : undefined, + isTruthy(gallery?.slug), ); useEffect(() => { @@ -187,6 +188,8 @@ const Create = ({ showDelete={isTruthy(gallery)} isProcessing={processing} galleryCoverUrl={galleryCoverImageUrl} + isSavingDraft={isSaving} + draftId={draft.id ?? undefined} onCoverClick={({ currentTarget }: MouseEvent) => { currentTarget.blur(); setGallerySliderActiveTab(GalleryFormSliderTabs.Cover); diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index cb4fefcdf..dd11f9956 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -21,6 +21,7 @@ interface GalleryDraft { interface GalleryDraftsState { reachedLimit: boolean; + isSaving: boolean; draft: GalleryDraft; setDraftCover: (image: ArrayBuffer | null, type: string | null) => void; setDraftNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void; @@ -36,7 +37,7 @@ const initialGalleryDraft: GalleryDraft = { id: null, }; -export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { +export const useGalleryDrafts = (givenDraftId?: number, disabled?: boolean): GalleryDraftsState => { const { wallet } = useAuth(); const database = useIndexedDB("gallery-drafts"); @@ -53,7 +54,7 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { // populate `draft` state if `givenDraftId` is present useEffect(() => { - if (givenDraftId === undefined) return; + if (givenDraftId === undefined || disabled === true) return; const getDraft = async (): Promise => { const draft: GalleryDraft | undefined = await database.getByID(givenDraftId); if (draft !== undefined && draft.walletAddress === wallet?.address) { @@ -65,7 +66,7 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { }, [givenDraftId, wallet?.address]); useEffect(() => { - if (!save || isSaving || reachedLimit) return; + if (disabled === true || !save || isSaving || reachedLimit) return; void saveDraft(); }, [save]); @@ -77,6 +78,7 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { const walletDrafts = await getWalletDrafts(); if (walletDrafts.length >= MAX_DRAFT_LIMIT_PER_WALLET) { + setIsSaving(false); setReachedLimit(true); return; } @@ -123,7 +125,7 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { }; const deleteDraft = async (): Promise => { - if (draft.id === null) return; + if (disabled === true || draft.id === null) return; await database.deleteRecord(draft.id); setReachedLimit(false); @@ -131,6 +133,7 @@ export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => { return { reachedLimit, + isSaving, draft, setDraftCover, setDraftNfts, diff --git a/resources/js/icons.tsx b/resources/js/icons.tsx index 7dcd27cb2..0f79c336b 100644 --- a/resources/js/icons.tsx +++ b/resources/js/icons.tsx @@ -43,6 +43,7 @@ import { ReactComponent as Explorer } from "@icons/explorer.svg"; import { ReactComponent as Eye } from "@icons/eye.svg"; import { ReactComponent as FatArrowDown } from "@icons/fat-arrow-down.svg"; import { ReactComponent as FatArrowUp } from "@icons/fat-arrow-up.svg"; +import { ReactComponent as FatDoubleCheck } from "@icons/fat-double-check.svg"; import { ReactComponent as FatPlus } from "@icons/fat-plus.svg"; import { ReactComponent as FatXInCircle } from "@icons/fat-x-in-circle.svg"; import { ReactComponent as FingerTap } from "@icons/finger-tap.svg"; @@ -210,4 +211,5 @@ export const SvgCollection = { Moralis, Mnemonic, Menu, + FatDoubleCheck, }; From bce9e6239dcb0a0dd1d2c993b868a35f975e1935 Mon Sep 17 00:00:00 2001 From: shahin-hq Date: Wed, 1 Nov 2023 20:14:29 +0000 Subject: [PATCH 21/35] Optimize 1 SVG(s) --- resources/icons/fat-double-check.svg | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/resources/icons/fat-double-check.svg b/resources/icons/fat-double-check.svg index 43ef2f78c..2d0db3d1b 100644 --- a/resources/icons/fat-double-check.svg +++ b/resources/icons/fat-double-check.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file From 1e12f9b8ac191b877fa6cbf8d7933e1863a4c942 Mon Sep 17 00:00:00 2001 From: ItsANameToo Date: Wed, 1 Nov 2023 20:14:48 +0000 Subject: [PATCH 22/35] Optimize 1 SVG(s) --- resources/icons/fat-double-check.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/icons/fat-double-check.svg b/resources/icons/fat-double-check.svg index 2d0db3d1b..d4efdd92f 100644 --- a/resources/icons/fat-double-check.svg +++ b/resources/icons/fat-double-check.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 223a1532f074ec2b672e0bb24755ba25225db716 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 00:15:30 +0400 Subject: [PATCH 23/35] wip --- resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts index a1b63c8bc..ef6ea78cb 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -188,6 +188,7 @@ describe("useGalleryDrafts", () => { await waitFor(() => { expect(addMock).not.toHaveBeenCalled(); expect(result.current.reachedLimit).toBe(true); + expect(result.current.isSaving).toBe(false); }); }); From d013354442b3dddd1b0c3937e8cf5f7beb41aa12 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 15:51:20 +0400 Subject: [PATCH 24/35] fix tests --- resources/js/Components/Sidebar/SidebarItem.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/Components/Sidebar/SidebarItem.test.tsx b/resources/js/Components/Sidebar/SidebarItem.test.tsx index 73a962fbe..fbc9dc36c 100644 --- a/resources/js/Components/Sidebar/SidebarItem.test.tsx +++ b/resources/js/Components/Sidebar/SidebarItem.test.tsx @@ -8,6 +8,7 @@ describe("SidebarItem", () => { , ); @@ -20,6 +21,7 @@ describe("SidebarItem", () => { icon="Cog" title="General" rightText="1234" + href="/hello" />, ); From cc12a80d037ec28707bc6fa0b791567ac3e66816 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 16:00:57 +0400 Subject: [PATCH 25/35] fix tests --- resources/js/Components/Sidebar/SidebarItem.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/Components/Sidebar/SidebarItem.test.tsx b/resources/js/Components/Sidebar/SidebarItem.test.tsx index fbc9dc36c..5d4db1a54 100644 --- a/resources/js/Components/Sidebar/SidebarItem.test.tsx +++ b/resources/js/Components/Sidebar/SidebarItem.test.tsx @@ -13,6 +13,8 @@ describe("SidebarItem", () => { ); expect(screen.getByTestId("SidebarItem")).toBeInTheDocument(); + + expect(screen.queryByText("1234")).not.toBeInTheDocument(); }); it("should render with rightText", () => { From 55cf480754adaf3ec2b7d76136a0140aeccdf65e Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 16:15:53 +0400 Subject: [PATCH 26/35] fix tests --- .../GalleryDraftStatus.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.test.tsx diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.test.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.test.tsx new file mode 100644 index 000000000..7736c20fb --- /dev/null +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.test.tsx @@ -0,0 +1,34 @@ +import { expect } from "vitest"; +import { GalleryDraftStatus } from "@/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus"; +import { render, screen } from "@/Tests/testing-library"; + +describe("GalleryDraftStatus", () => { + it("should show draft saved status", () => { + render( + , + ); + + expect(screen.getByText(/Draft Saved/)).toBeInTheDocument(); + }); + + it("should show saving status", () => { + render( + , + ); + + expect(screen.getByText(/Saving to draft/)).toBeInTheDocument(); + }); + + it("should not render any status message", () => { + render(); + + expect(screen.queryByText(/Saving to draft/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Draft Saved/)).not.toBeInTheDocument(); + }); +}); From a54a9f18b7fe53e99077dae41eff5df4872cf96a Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 16:29:47 +0400 Subject: [PATCH 27/35] fix tests --- .../GalleryActionToolbar.test.tsx | 25 +++++++++++++++++++ .../GalleryActionToolbar.tsx | 2 ++ 2 files changed, 27 insertions(+) diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.test.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.test.tsx index 90fa7cb1b..f8c991262 100644 --- a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.test.tsx +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.test.tsx @@ -29,6 +29,31 @@ describe("GalleryActionToolbar", () => { expect(screen.getByTestId("GalleryActionToolbar__publish")).toBeDisabled(); }); + it("should show saving to draft icon", () => { + render( + , + ); + + expect(screen.getByTestId("Icon_SavingDraft")).toBeInTheDocument(); + }); + + it("should show draft saved icon", () => { + render( + , + ); + + expect(screen.getByTestId("Icon_DraftSaved")).toBeInTheDocument(); + }); + it.each(allBreakpoints)("should render without delete button in %s screen", (breakpoint) => { render(, { breakpoint }); diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx index ef8ef1dd5..9bd29396a 100644 --- a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryActionToolbar.tsx @@ -97,6 +97,7 @@ export const GalleryActionToolbar = ({ )} @@ -105,6 +106,7 @@ export const GalleryActionToolbar = ({ )} From 39491b1cb0a8e5fd8785f8df02c7dfa79656da90 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 16:53:05 +0400 Subject: [PATCH 28/35] fix tests --- .../js/Components/Sidebar/SidebarItem.test.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/resources/js/Components/Sidebar/SidebarItem.test.tsx b/resources/js/Components/Sidebar/SidebarItem.test.tsx index 5d4db1a54..029c94a9f 100644 --- a/resources/js/Components/Sidebar/SidebarItem.test.tsx +++ b/resources/js/Components/Sidebar/SidebarItem.test.tsx @@ -31,4 +31,18 @@ describe("SidebarItem", () => { expect(screen.getByText("1234")).toBeInTheDocument(); }); + + it("should render disabled with rightText", () => { + render( + , + ); + + expect(screen.getByTestId("SidebarItem__disabled")).toBeInTheDocument(); + + expect(screen.getByText("1234")).toBeInTheDocument(); + }); }); From e89e4bb39fb6cec77f8695a179b120f71b63b57f Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 16:59:02 +0400 Subject: [PATCH 29/35] fix tests --- lang/en/pages.php | 2 ++ .../GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lang/en/pages.php b/lang/en/pages.php index 42096bf48..febc51c13 100644 --- a/lang/en/pages.php +++ b/lang/en/pages.php @@ -200,6 +200,8 @@ 'can_purchase' => 'You can purchase NFTs with these top NFT Marketplaces:', 'must_own_one_nft' => 'You must own at least one (1) NFT in order to create a gallery.', 'back_to_galleries' => 'Back to Galleries', + 'draft_saved' => 'Draft Saved', + 'saving_to_draft' => 'Saving to draft', ], 'delete_modal' => [ 'title' => 'Delete Gallery', diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx index 593863fc5..ce05eb9ae 100644 --- a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import { Icon } from "@/Components/Icon"; import { isTruthy } from "@/Utils/is-truthy"; @@ -8,6 +9,8 @@ export const GalleryDraftStatus = ({ draftId?: number; isSavingDraft?: boolean; }): JSX.Element => { + const { t } = useTranslation(); + if (isTruthy(draftId) && isSavingDraft === false) { return (
@@ -17,7 +20,7 @@ export const GalleryDraftStatus = ({ className="text-theme-secondary-700" />
- Draft Saved + {t("pages.galleries.create.draft_saved")}
); @@ -33,6 +36,7 @@ export const GalleryDraftStatus = ({ />
Saving to draft + {t("pages.galleries.create.saving_to_draft")}
); From cc1c30c04176447df24c70e2856dfd4ad6b55676 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 17:15:44 +0400 Subject: [PATCH 30/35] fix tests --- resources/js/I18n/Locales/en.json | 2 +- .../Pages/Galleries/hooks/useGalleryDrafts.test.ts | 14 ++++++++++++++ .../js/Pages/Galleries/hooks/useGalleryDrafts.ts | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/resources/js/I18n/Locales/en.json b/resources/js/I18n/Locales/en.json index 97ff095df..ec647418c 100644 --- a/resources/js/I18n/Locales/en.json +++ b/resources/js/I18n/Locales/en.json @@ -1 +1 @@ -{"auth.welcome":"Welcome to Dashbrd","auth.logged_in":"You're logged in!","auth.log_out":"Log out","auth.failed":"These credentials do not match our records.","auth.session_timeout":"Your session has timed out. Please refresh the page and try connecting your account again.","auth.session_timeout_modal":"Seems like your session has timed out. Please connnect your wallet again.","auth.password":"The provided password is incorrect.","auth.throttle":"Too many login attempts. Please try again in {{seconds}} seconds.","auth.wallet.connecting":"Connecting …","auth.wallet.waiting_for_signature":"Waiting for Signature …","auth.wallet.switching_wallet":"Switching Wallet …","auth.wallet.connect":"Connect Wallet","auth.wallet.connect_long":"Connect Your Wallet to Get Started","auth.wallet.sign_subtitle":"Connect Your Wallet to Continue","auth.wallet.disconnect":"Disconnect Wallet","auth.wallet.install":"Install MetaMask","auth.wallet.install_long":"Install MetaMask to Get Started","auth.wallet.sign":"Sign Message","auth.wallet.sign_message":"Welcome to Dashbrd. In order to login, sign this message with your wallet. It doesn't cost you anything!\n\nSigning ID (you can ignore this): {{nonce}}","auth.wallet.connect_subtitle":"Click on the MetaMask icon in your browser to confirm the action and connect your wallet.","auth.wallet.requires_signature":"In order to prevent impersonation, we require a signature to perform this action. This signature is only a signed message and does not give any access to your wallet.","auth.errors.metamask.no_account":"No account found. Please connect your wallet and try again.","auth.errors.metamask.generic":"Connection attempt error. Please retry and follow the steps to connect your wallet.","auth.errors.metamask.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","auth.errors.metamask.provider_missing":"You don't have MetaMask installed in your browser. Please install and try again.","auth.errors.metamask.user_rejected":"It looks like you cancelled signing of the authentication message. Please try again.","auth.errors.metamask.provider_not_set":"Ethereum provider is not set","auth.validation.wallet_login_failed":"There was a problem trying to verify your signature. Please try again.","auth.validation.invalid_address":"Your wallet address is invalid. Please try again.","auth.validation.invalid_signature":"Signature is invalid. Please try again.","auth.validation.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","common.add":"Add","common.amount":"Amount","common.balance":"Balance","common.cancel":"Cancel","common.delete":"Delete","common.edit":"Edit","common.confirm":"Confirm","common.connect":"Connect","common.continue":"Continue","common.done":"Done","common.filter":"Filter","common.items":"Items","common.receive":"Receive","common.received":"Received","common.retry":"Retry","common.records":"Records","common.save":"Save","common.send":"Send","common.sent":"Sent","common.show":"Show","common.searching":"Searching...","common.other":"Other","common.owned":"Owned","common.token":"Token","common.tokens":"Tokens","common.wallet":"Wallet","common.pending":"Pending","common.publish":"Publish","common.empty":"Empty","common.your_address":"Your Address","common.warning":"Warning","common.my_address":"My Address","common.my_wallet":"My Wallet","common.my_balance":"My Balance","common.na":"N/A","common.simple_plural_without_data":"One comment|Many comments","common.simple_plural_with_data":"{{count}} comment|{{count}} comments","common.advanced_plural_without_data":"{0} no comments yet|{1} 1 comment|[2,*] Many comments","common.advanced_plural_with_data":"{0} no comments yet|{1} 1 comment|[2,*] {{count}} comments","common.copy_clipboard":"Copy to Clipboard","common.copy":"Copy","common.download":"Download","common.zoom":"Zoom","common.my_collection":"My Collection","common.max":"Max","common.chain":"Chain","common.copied":"Copied!","common.coming_soon":"Coming Soon","common.more_details":"More Details","common.close":"Close","common.close_toast":"Close toast","common.loading":"Loading","common.price":"Price","common.market_cap":"Market Cap","common.volume":"Volume {{frequency}}","common.value":"Value","common.last_n_days":"Last {{count}} Days","common.details":"Details","common.view_all":"View All","common.view_more_on_polygonscan":"View More on Polygonscan","common.view_more_on_etherscan":"View More on Etherscan","common.view_nft_on_etherscan":"View NFT on Etherscan","common.view_nft_on_polygonscan":"View NFT on Polygonscan","common.view_nft_on_goerli_tesnet":"View NFT on Goerli Testnet Explorer","common.view_nft_on_mumbai_tesnet":"View NFT on Mumbai Testnet Explorer","common.polygonscan":"Polygonscan","common.etherscan":"Etherscan","common.featured":"Featured","common.floor_price":"Floor Price","common.floor":"Floor","common.supply":"Supply","common.owners":"Owners","common.created":"Created","common.nft":"NFT","common.collection":"Collection","common.collections":"Collections","common.gallery":"Gallery","common.basic_gallery":"Basic Gallery","common.gallery_name":"Gallery Name","common.create_gallery":"Create Gallery","common.external_link":"External Link","common.follow_link":"Follow Link","common.cover":"Cover","common.template":"Template","common.page":"Page","common.polygon":"Polygon","common.ethereum":"Ethereum","common.mumbai":"Mumbai","common.goerli":"Goerli","common.of":"of","common.pagination_input_placeholder":"Enter the page number","common.pagination_input_placeholder_mobile":"Page number","common.nft_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","common.nft_gallery":"NFT Gallery","common.unable_to_retrieve_image":"Unable to retrieve your NFT image at this time","common.optional":"Optional","common.selected":"Selected","common.select":"Select","common.preview":"Preview","common.image_size_error":"Image size must not exceed 2MB","common.image_dimensions_error":"Image dimensions must exceed 287px x 190px","common.write_to_confirm":"Write {{word}} to confirm","common.n_hours":"{0} {{count}} hour|{1} {{count}} hour|[2,*] {{count}} hours","common.n_minutes":"{0} {{count}} minute|{1} {{count}} minute|[2,*] {{count}} minutes","common.report":"Report","common.n_nfts":"{0}NFTs|{1}NFT|[2,*]NFTs","common.n_collections":"{0}collections|{1}collection|[2,*]collections","common.back":"Back","common.back_to":"Back to","common.name":"Name","common.type":"Type","common.from":"From","common.to":"To","common.sale_price":"Sale Price","common.recent_activity":"Recent Activity","common.time":"Time","common.datetime.few_seconds_ago":"A few seconds ago","common.datetime.minutes_ago":"{0} Less than a minute ago|{1} A minute ago|[2,*] {{count}} minutes ago","common.datetime.hours_ago":"{0} Less than an hour ago|{1} An hour ago|[2,*] {{count}} hours ago","common.datetime.days_ago":"{0} Less than a day ago|{1} A day ago|[2,*] {{count}} days ago","common.datetime.weeks_ago":"{0} Less than a week ago|{1} A week ago|[2,*] {{count}} weeks ago","common.datetime.months_ago":"{0} Less than a month ago|{1} A month ago|[2,*] {{count}} months ago","common.datetime.years_ago":"{0} Less than a year ago|{1} A year ago|[2,*] {{count}} years ago","common.no_traits_found":"No traits found.","common.empty_transactions":"You don't have any transactions yet. Once transactions have been made, they will show up here.","common.error":"Error","common.refresh_metadata":"Refresh Metadata","common.refreshing_metadata":"Refreshing Metadata... Please check back later.","common.pending_confirmation":"Pending Confirmation","common.confirmed_transaction":"Confirmed Transaction","common.transaction_error":"Transaction Error","common.transaction_error_description_first_part":"Your transaction encountered an error. View this transaction on","common.transaction_error_description_second_part":"for more details.","common.home":"Home","common.contact":"Contact","common.menu":"Menu","common.website":"Website","common.twitter":"Twitter","common.discord":"Discord","common.sort":"Sort","footer.copyright":"{{year}} © Dashbrd. All rights reserved.","footer.all_rights_reserved":"All rights reserved","footer.privacy_policy":"Privacy Policy","footer.terms_of_service":"Terms of Service","footer.powered_by":"Powered by","format.fiat":"{{ value, currency }}","format.number":"{{ value, number }}","metatags.home.title":"Dashbrd | Web3 Portfolio Management Made Simple","metatags.home.description":"Simplify your Web3 journey with Dashbrd. Manage your portfolio of tokens, NFTs, and other digital collectibles across the Ethereum and Polygon blockchains.","metatags.home.image":"/images/meta/home.png","metatags.error.title":"Error {{code}} | Dashbrd","metatags.wallet.title":"My Wallet | Dashbrd","metatags.galleries.title":"Top NFT Galleries | Dashbrd","metatags.galleries.image":"/images/meta/nft-galleries.png","metatags.galleries.description":"Explore user published NFT galleries to find custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.most_popular.title":"Most Popular Galleries | Dashbrd","metatags.galleries.most_popular.image":"/images/meta/most-popular-nft-galleries.png","metatags.galleries.most_popular.description":"Explore and discover Most Popular NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.newest.title":"Newest Galleries | Dashbrd","metatags.galleries.newest.image":"/images/meta/newest-nft-galleries.png","metatags.galleries.newest.description":"Explore and discover most recent NFT galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More","metatags.galleries.most_valuable.title":"Most Valuable Galleries | Dashbrd","metatags.galleries.most_valuable.image":"/images/meta/most-valuable-nft-galleries.png","metatags.galleries.most_valuable.description":"Explore and discover Most Valuable NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.view.title":"{{name}} | Dashbrd","metatags.galleries.view.description":"{{name}} | A Curated NFT Gallery at Dashbrd","metatags.galleries.view.image":"/images/meta/nft-gallery.png","metatags.my_galleries.title":"My Galleries | Dashbrd","metatags.my_galleries.create.title":"Create Gallery | Dashbrd","metatags.my_galleries.edit.title":"Edit {{name}} | Dashbrd","metatags.collections.title":"Collections | Dashbrd","metatags.collections.view.title":"{{name}} Collection | Dashbrd","metatags.collections.view.description":"Immerse yourself in the intricate details of {{name}} collection, featuring remarkable digital assets. Start your NFT journey today!","metatags.collections.view.image":"/images/meta/nft-collection.png","metatags.nfts.view.title":"{{nft}} NFT | Dashbrd","metatags.nfts.view.description":"Uncover the complete story of {{nft}} NFT from the {{collection}} collection, delving into its unique attributes and distinctive features.","metatags.nfts.view.image":"/images/meta/nft-details.png","metatags.settings.title":"Settings | Dashbrd","metatags.login.title":"Login | Dashbrd","metatags.privacy_policy.title":"Privacy Policy | Dashbrd","metatags.privacy_policy.description":"Dashbrd’s privacy policy outlines the information we collect and explains your choices surrounding how we use information about you.","metatags.terms_of_service.title":"Terms of Service | Dashbrd","metatags.terms_of_service.description":"These Terms of Service cover your use and access to services, products or websites of Dashbrd.","metatags.cookie_policy.title":"Cookie Policy | Dashbrd","metatags.cookie_policy.description":"Dashbrd uses cookies to make the website more user-friendly. Find out about the main types of cookies we use, and what we use them for.","metatags.welcome.title":"Welcome to Dashbrd | Web3 Simplified","pages.onboarding.title":"Get Started","pages.onboarding.heading":"Your monkeys were bored and ran off, we are trying to round them up.","pages.onboarding.message":"We are setting up your account. This process usually takes just a few minutes, but can take up to 15 minutes.","pages.error.heading":"Oops, something went wrong ...","pages.error.message":"Please try again or get in touch if the issue persists.","pages.maintenance.title":"Dashbrd is currently down for scheduled maintenance.","pages.maintenance.description":"We expect to be back soon. Thanks for your patience.","pages.dashboard.title":"My Wallet","pages.dashboard.breakdown.title":"Portfolio Breakdown","pages.dashboard.line_chart.data_error":"Could not load chart data","pages.collections.title":"Collections","pages.collections.collections":"Collections","pages.collections.collection_value":"Collection Value","pages.collections.nfts_owned":"NFTs Owned","pages.collections.header_title":"You own <0>{{nftsCount}} {{nfts}} across <0>{{collectionsCount}} {{collections}}, worth about <0><1>{{worth}}","pages.collections.search_placeholder":"Search by Collection","pages.collections.properties":"Properties","pages.collections.collections_network":"Collections Network","pages.collections.property_search_placeholder":"Feature Search","pages.collections.floor_price":"Floor Price","pages.collections.value":"Value","pages.collections.rarity":"Rarity","pages.collections.report":"Report","pages.collections.hide_collection":"Hide Collection","pages.collections.unhide_collection":"Unhide Collection","pages.collections.no_collections":"You do not own any NFTs yet. Once you do they will be shown here.","pages.collections.all_collections_hidden":"You have hidden all your collections. Unhide and they will appear here.","pages.collections.about_collection":"About Collection","pages.collections.show_hidden":"Show Hidden","pages.collections.show_my_collection":"Show My Collection","pages.collections.owned":"Owned","pages.collections.activities.loading_activities":"We're fetching Activity for this NFT, please hang tight, this can take a while.","pages.collections.activities.ignores_activities":"We don't support activity history for this collection yet.","pages.collections.activities.no_activity":"This collection does not have any activity yet.","pages.collections.activities.types.LABEL_MINT":"Mint","pages.collections.activities.types.LABEL_TRANSFER":"Transfer","pages.collections.activities.types.LABEL_SALE":"Sale","pages.collections.search.loading_results":"Loading results...","pages.collections.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.collections.search.no_results_with_filters":"We could not find anything matching your filters, please try again!","pages.collections.search.no_results_ownership":"You do not own any NFTs in this collection","pages.collections.search.error":"Could not load search results. Please try again later.","pages.collections.sorting.token_number":"Token Number","pages.collections.sorting.recently_received":"Recently Received","pages.collections.sorting.recently_created":"Recently Created","pages.collections.sorting.oldest_collection":"Oldest Collection","pages.collections.traits.description":"List of NFT traits by % of occurrence in the collection","pages.collections.traits.no_traits":"No Properties can be found for this NFT","pages.collections.menu.collection":"Collection","pages.collections.menu.activity":"Activity","pages.collections.hidden_modal.collection_hidden":"Collection Hidden","pages.collections.hidden_modal.description":"This collection is currently set to Hidden. Are you sure you want to unhide this collection? You can\n reset the collection to hidden from the collection menu.","pages.collections.hidden_modal.unhide":"Unhide","pages.collections.hidden_modal.error":"Something went wrong. Please try again.","pages.collections.external_modal.you_wish_continue":"You are about to leave Dashbrd to an external website. Dashbrd has no control over the content of\n this site. Are you sure you wish to continue?","pages.collections.external_modal.not_show":"Do not show this message again.","pages.collections.refresh.title":"Refresh your collection","pages.collections.refresh.notice":"You can refresh data every 15 minutes.","pages.collections.refresh.notice_wait":"Please wait. You can refresh data every 15 minutes.","pages.collections.refresh.toast":"We're updating information for your collection.","pages.nfts.nft":"nft","pages.nfts.about_nft":"About NFT","pages.nfts.owned_by":"Owned by","pages.nfts.collection_image":"collection image","pages.nfts.menu.properties":"Properties","pages.nfts.menu.activity":"Activity","pages.reports.title":"Submit a Report","pages.reports.description":"Thanks for looking out by reporting things that break the rules. Let us know what's happening and we'll receive the report.","pages.reports.success":"Thank you for your report. We'll review it and see if it breaks our ToS.","pages.reports.failed":"Something went wrong. Please try again.","pages.reports.throttle":"You have made too many requests. Please wait {{time}} before reporting again.","pages.reports.reported":"You have already reported this {{model}}.","pages.reports.reasons.spam":"Spam","pages.reports.reasons.violence":"Promoting Violence","pages.reports.reasons.hate":"Hate","pages.reports.reasons.inappropriate_content":"Inappropriate Content","pages.reports.reasons.impersonation":"Impersonation","pages.reports.reasons.trademark":"Trademark or Copyright","pages.reports.reasons.selfharm":"Self-Harm","pages.reports.reasons.harassment":"Harassment","pages.galleries.title":"Galleries","pages.galleries.empty_title":"No galleries have been published yet. Once they do they will appear here.","pages.galleries.search.loading_results":"Loading results...","pages.galleries.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.search.placeholder":"Search by Galleries","pages.galleries.search.placeholder_nfts":"Search by NFTs","pages.galleries.search.error":"Could not load search results. Please try again later.","pages.galleries.my_galleries.title":"My Galleries","pages.galleries.my_galleries.new_gallery":"New Gallery","pages.galleries.my_galleries.no_galleries":"You have not created any galleries yet. To create a gallery, click on the \"Create Gallery\" button.","pages.galleries.my_galleries.succesfully_deleted":"Gallery successfully deleted","pages.galleries.my_galleries.successfully_created":"Gallery has been successfully created","pages.galleries.my_galleries.successfully_updated":"Gallery has been successfully updated","pages.galleries.my_galleries.new_gallery_no_nfts":"Creating a Gallery requires you to own an NFT.","pages.galleries.copy_gallery_link":"Copy Gallery Link","pages.galleries.my_nfts":"My NFTs","pages.galleries.value":"Value","pages.galleries.floor_price":"Floor Price","pages.galleries.nfts":"NFTs","pages.galleries.collections":"Collections","pages.galleries.galleries_count_simple":"{0} galleries|{1} gallery|[2,*] galleries","pages.galleries.galleries_count":"{0} {{count}} Galleries|{1} {{count}} Gallery|[2,*] {{count}} Galleries","pages.galleries.collections_count_simple":"{0} collections|{1} collection|[2,*] collections","pages.galleries.collections_count":"{0} {{count}} Collections|{1} {{count}} Collection|[2,*] {{count}} Collections","pages.galleries.nfts_count_simple":"{0} NFTs|{1} NFT|[2,*] NFTs","pages.galleries.nfts_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","pages.galleries.users_count_simple":"{0} users|{1} user|[2,*] users","pages.galleries.users_count":"{0} {{count}} Users|{1} {{count}} User|[2,*] {{count}} Users","pages.galleries.featuring":"Featuring","pages.galleries.curated_by":"Curated by","pages.galleries.worth_about":"Worth About","pages.galleries.valued_at":"valued at","pages.galleries.from":"From","pages.galleries.most_popular_galleries":"Most Popular Galleries","pages.galleries.newest_galleries":"Newest Galleries","pages.galleries.most_valuable_galleries":"Most Valuable Galleries","pages.galleries.most_popular":"Most Popular","pages.galleries.newest":"Newest","pages.galleries.most_valuable":"Most Valuable","pages.galleries.create.search_by_nfts":"Search by NFTs","pages.galleries.create.input_placeholder":"Enter gallery name","pages.galleries.create.title_too_long":"Gallery name must not exceed {{max}} characters.","pages.galleries.create.already_selected_nft":"NFT already exists in this gallery","pages.galleries.create.nft_missing_image":"Only NFTs with images can be added to galleries","pages.galleries.create.gallery_cover":"Gallery Cover","pages.galleries.create.gallery_cover_description":"The cover is used for the card on the gallery list page. While the cover is not a requirement it will allow you to add personality and stand out from the crowd.","pages.galleries.create.gallery_cover_information":"Image dimensions must be at least 287px x 190px, with a max size of 2 MB (JPG, PNG or GIF)","pages.galleries.create.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.create.templates.cover":"Cover","pages.galleries.create.templates.template":"Template","pages.galleries.create.templates.select":"Select Gallery Template","pages.galleries.create.templates.basic":"Basic Gallery","pages.galleries.create.templates.coming_soon":"More Coming Soon","pages.galleries.create.load_more_collections_one":"Load {{count}} More Collection","pages.galleries.create.load_more_collections_other":"Load {{count}} More Collections","pages.galleries.create.load_more_nfts":"Load More NFTs","pages.galleries.create.can_purchase":"You can purchase NFTs with these top NFT Marketplaces:","pages.galleries.create.must_own_one_nft":"You must own at least one (1) NFT in order to create a gallery.","pages.galleries.create.back_to_galleries":"Back to Galleries","pages.galleries.delete_modal.title":"Delete Gallery","pages.galleries.delete_modal.confirmation_text":"Are you sure you want to delete the gallery? Everything you've done will be deleted and you won't be able to get it back.","pages.galleries.consists_of_collections":"{0} This gallery consists of {{count}} collections|{1} This gallery consists of {{count}} collection|[2,*] This gallery consists of {{count}} collections","pages.galleries.guest_banner.title":"Craft the ultimate","pages.galleries.guest_banner.subtitle":"Pick your favorites, curate your gallery, & share it with the world.","pages.profile.title":"Profile","pages.token_panel.balance_tooltip":"Total percentage of the portfolio held in this token","pages.token_panel.insufficient_funds":"Insufficient Balance","pages.token_panel.error":"Dashbrd has failed to load token information. Please try again later.","pages.token_panel.failed_to_retrieve_transactions":"We were unable to fetch your transactions.","pages.token_panel.tabs.transaction_history":"Transaction History","pages.token_panel.tabs.history":"History","pages.token_panel.tabs.market_data":"Market Data","pages.token_panel.details.current_price":"Current Price","pages.token_panel.details.title":"Token Details","pages.token_panel.details.market_cap":"Market Cap","pages.token_panel.details.volume":"Daily Volume","pages.token_panel.details.supply":"Minted Supply","pages.token_panel.details.ath":"All-Time High","pages.token_panel.details.atl":"All-Time Low","pages.token_panel.chart.failed":"Dashbrd has failed to load chart information. Please try again later.","pages.transaction_details_panel.title":"Transaction Details","pages.transaction_details_panel.details.blockchain":"Blockchain","pages.transaction_details_panel.details.timestamp":"Timestamp","pages.transaction_details_panel.details.transaction_hash":"Transaction Hash","pages.transaction_details_panel.details.transaction_fee":"Transaction Fee","pages.transaction_details_panel.details.gas_price":"Gas Price","pages.transaction_details_panel.details.gas_used":"Gas Used","pages.transaction_details_panel.details.nonce":"Nonce","pages.send_receive_panel.send.labels.token_and_amount":"Token and Amount","pages.send_receive_panel.send.labels.destination_address":"Destination Address","pages.send_receive_panel.send.labels.projected_fee":"Projected Fee","pages.send_receive_panel.send.placeholders.enter_amount":"Enter Amount","pages.send_receive_panel.send.placeholders.insert_recipient_address":"Insert Recipient Address","pages.send_receive_panel.send.placeholders.projected_fee":"Projected Fee","pages.send_receive_panel.send.errors.amount":"Insufficient Funds: You do not have enough to cover the amount + fee.","pages.send_receive_panel.send.errors.destination":"Destination address is not correct. Check and input again.","pages.send_receive_panel.send.hints.token_price":"Token Price","pages.send_receive_panel.send.fees.Fast":"Fast","pages.send_receive_panel.send.fees.Avg":"Avg","pages.send_receive_panel.send.fees.Slow":"Slow","pages.send_receive_panel.send.search_dropdown.placeholder":"Search token","pages.send_receive_panel.send.search_dropdown.no_results":"No results","pages.send_receive_panel.send.search_dropdown.error":"Error occurred while searching tokens.","pages.send_receive_panel.send.transaction_time":"Transaction Time: ~{{ time }} minutes","pages.send_receive_panel.send.from":"From","pages.send_receive_panel.send.to":"To","pages.send_receive_panel.send.amount":"Amount","pages.send_receive_panel.send.fee":"Fee","pages.send_receive_panel.send.total_amount":"Total Amount","pages.send_receive_panel.send.waiting_message":"Review and verify the information on your MetaMask. Sign to send the transaction.","pages.send_receive_panel.send.waiting_spinner_text":"Waiting for confirmation...","pages.send_receive_panel.send.failed_message":"It looks like something went wrong while sending your transaction. Press 'Retry' to make another attempt.","pages.send_receive_panel.receive.alert":"Send only Polygon or Ethereum Network compatible tokens to this address or you could permanently lose your funds!","pages.settings.title":"Settings","pages.settings.sidebar.general":"General","pages.settings.sidebar.notifications":"Notifications","pages.settings.sidebar.session_history":"Sessions History","pages.settings.general.title":"Settings","pages.settings.general.subtitle":"Customize your App Experience","pages.settings.general.currency":"Currency","pages.settings.general.currency_subtitle":"Select your default currency which will be used throughout the app.","pages.settings.general.time_date":"Time & Date","pages.settings.general.time_date_subtitle":"Select how you want time and date be shown inside app.","pages.settings.general.date_format":"Date Format","pages.settings.general.time_format":"Time Format","pages.settings.general.timezone":"Timezone","pages.settings.general.set_defaults":"Set Defaults","pages.settings.general.set_defaults_content":"Reverting to the default settings will remove any customizations previously made. Are you sure?","pages.settings.general.save":"Save Settings","pages.settings.general.saved":"Your settings have been successfully saved","pages.wallet.title":"Wallet","pages.privacy_policy.title":"Privacy Policy","pages.terms_of_service.title":"Terms of Service","pagination.previous":"« Previous","pagination.next":"Next »","passwords.reset":"Your password has been reset!","passwords.sent":"We have emailed your password reset link!","passwords.throttled":"Please wait before retrying.","passwords.token":"This password reset token is invalid.","passwords.user":"We can't find a user with that email address.","urls.landing":"https://dashbrd.com","urls.cookie_policy":"https://dashbrd.com/cookie-policy","urls.privacy_policy":"https://dashbrd.com/privacy-policy","urls.terms_of_service":"https://dashbrd.com/terms-of-service","urls.twitter":"https://x.com/DashbrdApp","urls.discord":"https://discord.gg/MJyWKkCJ5k","urls.github":"https://github.com/ArdentHQ/dashbrd","urls.coingecko":"https://www.coingecko.com","urls.etherscan":"https://etherscan.io","urls.polygonscan":"https://polygonscan.com","urls.alchemy":"https://www.alchemy.com","urls.moralis":"https://moralis.io","urls.mnemonic":"https://www.mnemonichq.com","urls.opensea":"https://opensea.io/","urls.explorers.etherscan.token_transactions":"https://etherscan.io/token/{{token}}?a={{address}}","urls.explorers.etherscan.addresses":"https://etherscan.io/address/{{address}}","urls.explorers.etherscan.transactions":"https://etherscan.io/tx/{{id}}","urls.explorers.etherscan.nft":"https://etherscan.io/nft/{{address}}/{{nftId}}","urls.explorers.polygonscan.token_transactions":"https://polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.polygonscan.addresses":"https://polygonscan.com/address/{{address}}","urls.explorers.polygonscan.transactions":"https://polygonscan.com/tx/{{id}}","urls.explorers.polygonscan.nft":"https://polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.mumbai.token_transactions":"https://mumbai.polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.mumbai.addresses":"https://mumbai.polygonscan.com/address/{{address}}","urls.explorers.mumbai.transactions":"https://mumbai.polygonscan.com/tx/{{id}}","urls.explorers.mumbai.nft":"https://mumbai.polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.goerli.token_transactions":"https://goerli.etherscan.io/token/{{token}}?a={{address}}","urls.explorers.goerli.addresses":"https://goerli.etherscan.io/address/{{address}}","urls.explorers.goerli.transactions":"https://goerli.etherscan.io/tx/{{id}}","urls.explorers.goerli.nft":"https://goerli.etherscan.io/nft/{{address}}/{{nftId}}","urls.marketplaces.opensea.collection":"https://opensea.io/assets/{{network}}/{{address}}","urls.marketplaces.opensea.nft":"https://opensea.io/assets/{{network}}/{{address}}/{{nftId}}","urls.marketplaces.rarible.collection":"https://rarible.com/collection/{{address}}/items","urls.marketplaces.rarible.nft":"https://rarible.com/token/{{address}}:{{nftId}}","urls.marketplaces.blur.collection":"https://blur.io/collection/{{address}}","urls.marketplaces.blur.nft":"https://blur.io/asset/{{address}}/{{nftId}}","urls.marketplaces.looksrare.collection":"https://looksrare.org/collections/{{address}}","urls.marketplaces.looksrare.nft":"https://looksrare.org/collections/{{address}}/{{nftId}}","validation.accepted":"The {{attribute}} must be accepted.","validation.accepted_if":"The {{attribute}} must be accepted when {{other}} is {{value}}.","validation.active_url":"The {{attribute}} is not a valid URL.","validation.after":"The {{attribute}} must be a date after {{date}}.","validation.after_or_equal":"The {{attribute}} must be a date after or equal to {{date}}.","validation.alpha":"The {{attribute}} must only contain letters.","validation.alpha_dash":"The {{attribute}} must only contain letters, numbers, dashes and underscores.","validation.alpha_num":"The {{attribute}} must only contain letters and numbers.","validation.array":"The {{attribute}} must be an array.","validation.ascii":"The {{attribute}} must only contain single-byte alphanumeric characters and symbols.","validation.before":"The {{attribute}} must be a date before {{date}}.","validation.before_or_equal":"The {{attribute}} must be a date before or equal to {{date}}.","validation.between.array":"The {{attribute}} must have between {{min}} and {{max}} items.","validation.between.file":"The {{attribute}} must be between {{min}} and {{max}} kilobytes.","validation.between.numeric":"The {{attribute}} must be between {{min}} and {{max}}.","validation.between.string":"The {{attribute}} must be between {{min}} and {{max}} characters.","validation.boolean":"The {{attribute}} field must be true or false.","validation.confirmed":"The {{attribute}} confirmation does not match.","validation.current_password":"The password is incorrect.","validation.date":"The {{attribute}} is not a valid date.","validation.date_equals":"The {{attribute}} must be a date equal to {{date}}.","validation.date_format":"The {{attribute}} does not match the format {{format}}.","validation.decimal":"The {{attribute}} must have {{decimal}} decimal places.","validation.declined":"The {{attribute}} must be declined.","validation.declined_if":"The {{attribute}} must be declined when {{other}} is {{value}}.","validation.different":"The {{attribute}} and {{other}} must be different.","validation.digits":"The {{attribute}} must be {{digits}} digits.","validation.digits_between":"The {{attribute}} must be between {{min}} and {{max}} digits.","validation.dimensions":"The {{attribute}} has invalid image dimensions.","validation.distinct":"The {{attribute}} field has a duplicate value.","validation.doesnt_end_with":"The {{attribute}} may not end with one of the following: {{values}}.","validation.doesnt_start_with":"The {{attribute}} may not start with one of the following: {{values}}.","validation.email":"The {{attribute}} must be a valid email address.","validation.ends_with":"The {{attribute}} must end with one of the following: {{values}}.","validation.enum":"The selected {{attribute}} is invalid.","validation.exists":"The selected {{attribute}} is invalid.","validation.file":"The {{attribute}} must be a file.","validation.filled":"The {{attribute}} field must have a value.","validation.gt.array":"The {{attribute}} must have more than {{value}} items.","validation.gt.file":"The {{attribute}} must be greater than {{value}} kilobytes.","validation.gt.numeric":"The {{attribute}} must be greater than {{value}}.","validation.gt.string":"The {{attribute}} must be greater than {{value}} characters.","validation.gte.array":"The {{attribute}} must have {{value}} items or more.","validation.gte.file":"The {{attribute}} must be greater than or equal to {{value}} kilobytes.","validation.gte.numeric":"The {{attribute}} must be greater than or equal to {{value}}.","validation.gte.string":"The {{attribute}} must be greater than or equal to {{value}} characters.","validation.image":"The {{attribute}} must be an image.","validation.in":"The selected {{attribute}} is invalid.","validation.in_array":"The {{attribute}} field does not exist in {{other}}.","validation.integer":"The {{attribute}} must be an integer.","validation.ip":"The {{attribute}} must be a valid IP address.","validation.ipv4":"The {{attribute}} must be a valid IPv4 address.","validation.ipv6":"The {{attribute}} must be a valid IPv6 address.","validation.json":"The {{attribute}} must be a valid JSON string.","validation.lowercase":"The {{attribute}} must be lowercase.","validation.lt.array":"The {{attribute}} must have less than {{value}} items.","validation.lt.file":"The {{attribute}} must be less than {{value}} kilobytes.","validation.lt.numeric":"The {{attribute}} must be less than {{value}}.","validation.lt.string":"The {{attribute}} must be less than {{value}} characters.","validation.lte.array":"The {{attribute}} must not have more than {{value}} items.","validation.lte.file":"The {{attribute}} must be less than or equal to {{value}} kilobytes.","validation.lte.numeric":"The {{attribute}} must be less than or equal to {{value}}.","validation.lte.string":"The {{attribute}} must be less than or equal to {{value}} characters.","validation.mac_address":"The {{attribute}} must be a valid MAC address.","validation.max.array":"The {{attribute}} must not have more than {{max}} items.","validation.max.file":"The {{attribute}} must not be greater than {{max}} kilobytes.","validation.max.numeric":"The {{attribute}} must not be greater than {{max}}.","validation.max.string":"The {{attribute}} must not be greater than {{max}} characters.","validation.max_digits":"The {{attribute}} must not have more than {{max}} digits.","validation.mimes":"The {{attribute}} must be a file of type: {{values}}.","validation.mimetypes":"The {{attribute}} must be a file of type: {{values}}.","validation.min.array":"The {{attribute}} must have at least {{min}} items.","validation.min.file":"The {{attribute}} must be at least {{min}} kilobytes.","validation.min.numeric":"The {{attribute}} must be at least {{min}}.","validation.min.string":"The {{attribute}} must be at least {{min}} characters.","validation.min_digits":"The {{attribute}} must have at least {{min}} digits.","validation.missing":"The {{attribute}} field must be missing.","validation.missing_if":"The {{attribute}} field must be missing when {{other}} is {{value}}.","validation.missing_unless":"The {{attribute}} field must be missing unless {{other}} is {{value}}.","validation.missing_with":"The {{attribute}} field must be missing when {{values}} is present.","validation.missing_with_all":"The {{attribute}} field must be missing when {{values}} are present.","validation.multiple_of":"The {{attribute}} must be a multiple of {{value}}.","validation.not_in":"The selected {{attribute}} is invalid.","validation.not_regex":"The {{attribute}} format is invalid.","validation.numeric":"The {{attribute}} must be a number.","validation.password.letters":"The {{attribute}} must contain at least one letter.","validation.password.mixed":"The {{attribute}} must contain at least one uppercase and one lowercase letter.","validation.password.numbers":"The {{attribute}} must contain at least one number.","validation.password.symbols":"The {{attribute}} must contain at least one symbol.","validation.password.uncompromised":"The given {{attribute}} has appeared in a data leak. Please choose a different {{attribute}}.","validation.present":"The {{attribute}} field must be present.","validation.prohibited":"The {{attribute}} field is prohibited.","validation.prohibited_if":"The {{attribute}} field is prohibited when {{other}} is {{value}}.","validation.prohibited_unless":"The {{attribute}} field is prohibited unless {{other}} is in {{values}}.","validation.prohibits":"The {{attribute}} field prohibits {{other}} from being present.","validation.regex":"The {{attribute}} format is invalid.","validation.required":"The {{attribute}} field is required.","validation.required_array_keys":"The {{attribute}} field must contain entries for: {{values}}.","validation.required_if":"The {{attribute}} field is required when {{other}} is {{value}}.","validation.required_if_accepted":"The {{attribute}} field is required when {{other}} is accepted.","validation.required_unless":"The {{attribute}} field is required unless {{other}} is in {{values}}.","validation.required_with":"The {{attribute}} field is required when {{values}} is present.","validation.required_with_all":"The {{attribute}} field is required when {{values}} are present.","validation.required_without":"The {{attribute}} field is required when {{values}} is not present.","validation.required_without_all":"The {{attribute}} field is required when none of {{values}} are present.","validation.same":"The {{attribute}} and {{other}} must match.","validation.size.array":"The {{attribute}} must contain {{size}} items.","validation.size.file":"The {{attribute}} must be {{size}} kilobytes.","validation.size.numeric":"The {{attribute}} must be {{size}}.","validation.size.string":"The {{attribute}} must be {{size}} characters.","validation.starts_with":"The {{attribute}} must start with one of the following: {{values}}.","validation.string":"The {{attribute}} must be a string.","validation.timezone":"The {{attribute}} must be a valid timezone.","validation.unique":"The {{attribute}} has already been taken.","validation.uploaded":"The {{attribute}} failed to upload.","validation.uppercase":"The {{attribute}} must be uppercase.","validation.url":"The {{attribute}} must be a valid URL.","validation.ulid":"The {{attribute}} must be a valid ULID.","validation.uuid":"The {{attribute}} must be a valid UUID.","validation.custom.attribute-name.rule-name":"custom-message","validation.unsupported_currency_code":"The currency code you provided is invalid or not supported.","validation.unsupported_period":"The period you provided is invalid or not supported.","validation.unsupported_token_symbol":"The token symbol you provided is invalid or not supported.","validation.gallery_title_required":"Gallery name is required.","validation.gallery_title_max_characters":"The gallery name should not exceed 50 characters.","validation.gallery_title_invalid":"The gallery name is invalid.","validation.nfts_required":"Please add at least one NFT.","validation.nfts_max_size":"Galleries can contain no more than {{limit}} NFTs","validation.invalid_nfts":"The NFT in position {{position}} is invalid, please select another one.","validation.invalid_cover":"You have selected an invalid cover image, please try another one."} \ No newline at end of file +{"auth.welcome":"Welcome to Dashbrd","auth.logged_in":"You're logged in!","auth.log_out":"Log out","auth.failed":"These credentials do not match our records.","auth.session_timeout":"Your session has timed out. Please refresh the page and try connecting your account again.","auth.session_timeout_modal":"Seems like your session has timed out. Please connnect your wallet again.","auth.password":"The provided password is incorrect.","auth.throttle":"Too many login attempts. Please try again in {{seconds}} seconds.","auth.wallet.connecting":"Connecting …","auth.wallet.waiting_for_signature":"Waiting for Signature …","auth.wallet.switching_wallet":"Switching Wallet …","auth.wallet.connect":"Connect Wallet","auth.wallet.connect_long":"Connect Your Wallet to Get Started","auth.wallet.sign_subtitle":"Connect Your Wallet to Continue","auth.wallet.disconnect":"Disconnect Wallet","auth.wallet.install":"Install MetaMask","auth.wallet.install_long":"Install MetaMask to Get Started","auth.wallet.sign":"Sign Message","auth.wallet.sign_message":"Welcome to Dashbrd. In order to login, sign this message with your wallet. It doesn't cost you anything!\n\nSigning ID (you can ignore this): {{nonce}}","auth.wallet.connect_subtitle":"Click on the MetaMask icon in your browser to confirm the action and connect your wallet.","auth.wallet.requires_signature":"In order to prevent impersonation, we require a signature to perform this action. This signature is only a signed message and does not give any access to your wallet.","auth.errors.metamask.no_account":"No account found. Please connect your wallet and try again.","auth.errors.metamask.generic":"Connection attempt error. Please retry and follow the steps to connect your wallet.","auth.errors.metamask.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","auth.errors.metamask.provider_missing":"You don't have MetaMask installed in your browser. Please install and try again.","auth.errors.metamask.user_rejected":"It looks like you cancelled signing of the authentication message. Please try again.","auth.errors.metamask.provider_not_set":"Ethereum provider is not set","auth.validation.wallet_login_failed":"There was a problem trying to verify your signature. Please try again.","auth.validation.invalid_address":"Your wallet address is invalid. Please try again.","auth.validation.invalid_signature":"Signature is invalid. Please try again.","auth.validation.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","common.add":"Add","common.amount":"Amount","common.balance":"Balance","common.cancel":"Cancel","common.delete":"Delete","common.edit":"Edit","common.confirm":"Confirm","common.connect":"Connect","common.continue":"Continue","common.done":"Done","common.filter":"Filter","common.items":"Items","common.receive":"Receive","common.received":"Received","common.retry":"Retry","common.records":"Records","common.save":"Save","common.send":"Send","common.sent":"Sent","common.show":"Show","common.searching":"Searching...","common.other":"Other","common.owned":"Owned","common.token":"Token","common.tokens":"Tokens","common.wallet":"Wallet","common.pending":"Pending","common.publish":"Publish","common.empty":"Empty","common.your_address":"Your Address","common.warning":"Warning","common.my_address":"My Address","common.my_wallet":"My Wallet","common.my_balance":"My Balance","common.na":"N/A","common.simple_plural_without_data":"One comment|Many comments","common.simple_plural_with_data":"{{count}} comment|{{count}} comments","common.advanced_plural_without_data":"{0} no comments yet|{1} 1 comment|[2,*] Many comments","common.advanced_plural_with_data":"{0} no comments yet|{1} 1 comment|[2,*] {{count}} comments","common.copy_clipboard":"Copy to Clipboard","common.copy":"Copy","common.download":"Download","common.zoom":"Zoom","common.my_collection":"My Collection","common.max":"Max","common.chain":"Chain","common.copied":"Copied!","common.coming_soon":"Coming Soon","common.more_details":"More Details","common.close":"Close","common.close_toast":"Close toast","common.loading":"Loading","common.price":"Price","common.market_cap":"Market Cap","common.volume":"Volume {{frequency}}","common.value":"Value","common.last_n_days":"Last {{count}} Days","common.details":"Details","common.view_all":"View All","common.view_more_on_polygonscan":"View More on Polygonscan","common.view_more_on_etherscan":"View More on Etherscan","common.view_nft_on_etherscan":"View NFT on Etherscan","common.view_nft_on_polygonscan":"View NFT on Polygonscan","common.view_nft_on_goerli_tesnet":"View NFT on Goerli Testnet Explorer","common.view_nft_on_mumbai_tesnet":"View NFT on Mumbai Testnet Explorer","common.polygonscan":"Polygonscan","common.etherscan":"Etherscan","common.featured":"Featured","common.floor_price":"Floor Price","common.floor":"Floor","common.supply":"Supply","common.owners":"Owners","common.created":"Created","common.nft":"NFT","common.collection":"Collection","common.collections":"Collections","common.gallery":"Gallery","common.basic_gallery":"Basic Gallery","common.gallery_name":"Gallery Name","common.create_gallery":"Create Gallery","common.external_link":"External Link","common.follow_link":"Follow Link","common.cover":"Cover","common.template":"Template","common.page":"Page","common.polygon":"Polygon","common.ethereum":"Ethereum","common.mumbai":"Mumbai","common.goerli":"Goerli","common.of":"of","common.pagination_input_placeholder":"Enter the page number","common.pagination_input_placeholder_mobile":"Page number","common.nft_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","common.nft_gallery":"NFT Gallery","common.unable_to_retrieve_image":"Unable to retrieve your NFT image at this time","common.optional":"Optional","common.selected":"Selected","common.select":"Select","common.preview":"Preview","common.image_size_error":"Image size must not exceed 2MB","common.image_dimensions_error":"Image dimensions must exceed 287px x 190px","common.write_to_confirm":"Write {{word}} to confirm","common.n_hours":"{0} {{count}} hour|{1} {{count}} hour|[2,*] {{count}} hours","common.n_minutes":"{0} {{count}} minute|{1} {{count}} minute|[2,*] {{count}} minutes","common.report":"Report","common.n_nfts":"{0}NFTs|{1}NFT|[2,*]NFTs","common.n_collections":"{0}collections|{1}collection|[2,*]collections","common.back":"Back","common.back_to":"Back to","common.name":"Name","common.type":"Type","common.from":"From","common.to":"To","common.sale_price":"Sale Price","common.recent_activity":"Recent Activity","common.time":"Time","common.datetime.few_seconds_ago":"A few seconds ago","common.datetime.minutes_ago":"{0} Less than a minute ago|{1} A minute ago|[2,*] {{count}} minutes ago","common.datetime.hours_ago":"{0} Less than an hour ago|{1} An hour ago|[2,*] {{count}} hours ago","common.datetime.days_ago":"{0} Less than a day ago|{1} A day ago|[2,*] {{count}} days ago","common.datetime.weeks_ago":"{0} Less than a week ago|{1} A week ago|[2,*] {{count}} weeks ago","common.datetime.months_ago":"{0} Less than a month ago|{1} A month ago|[2,*] {{count}} months ago","common.datetime.years_ago":"{0} Less than a year ago|{1} A year ago|[2,*] {{count}} years ago","common.no_traits_found":"No traits found.","common.empty_transactions":"You don't have any transactions yet. Once transactions have been made, they will show up here.","common.error":"Error","common.refresh_metadata":"Refresh Metadata","common.refreshing_metadata":"Refreshing Metadata... Please check back later.","common.pending_confirmation":"Pending Confirmation","common.confirmed_transaction":"Confirmed Transaction","common.transaction_error":"Transaction Error","common.transaction_error_description_first_part":"Your transaction encountered an error. View this transaction on","common.transaction_error_description_second_part":"for more details.","common.home":"Home","common.contact":"Contact","common.menu":"Menu","common.website":"Website","common.twitter":"Twitter","common.discord":"Discord","common.sort":"Sort","footer.copyright":"{{year}} © Dashbrd. All rights reserved.","footer.all_rights_reserved":"All rights reserved","footer.privacy_policy":"Privacy Policy","footer.terms_of_service":"Terms of Service","footer.powered_by":"Powered by","format.fiat":"{{ value, currency }}","format.number":"{{ value, number }}","metatags.home.title":"Dashbrd | Web3 Portfolio Management Made Simple","metatags.home.description":"Simplify your Web3 journey with Dashbrd. Manage your portfolio of tokens, NFTs, and other digital collectibles across the Ethereum and Polygon blockchains.","metatags.home.image":"/images/meta/home.png","metatags.error.title":"Error {{code}} | Dashbrd","metatags.wallet.title":"My Wallet | Dashbrd","metatags.galleries.title":"Top NFT Galleries | Dashbrd","metatags.galleries.image":"/images/meta/nft-galleries.png","metatags.galleries.description":"Explore user published NFT galleries to find custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.most_popular.title":"Most Popular Galleries | Dashbrd","metatags.galleries.most_popular.image":"/images/meta/most-popular-nft-galleries.png","metatags.galleries.most_popular.description":"Explore and discover Most Popular NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.newest.title":"Newest Galleries | Dashbrd","metatags.galleries.newest.image":"/images/meta/newest-nft-galleries.png","metatags.galleries.newest.description":"Explore and discover most recent NFT galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More","metatags.galleries.most_valuable.title":"Most Valuable Galleries | Dashbrd","metatags.galleries.most_valuable.image":"/images/meta/most-valuable-nft-galleries.png","metatags.galleries.most_valuable.description":"Explore and discover Most Valuable NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.view.title":"{{name}} | Dashbrd","metatags.galleries.view.description":"{{name}} | A Curated NFT Gallery at Dashbrd","metatags.galleries.view.image":"/images/meta/nft-gallery.png","metatags.my_galleries.title":"My Galleries | Dashbrd","metatags.my_galleries.create.title":"Create Gallery | Dashbrd","metatags.my_galleries.edit.title":"Edit {{name}} | Dashbrd","metatags.collections.title":"Collections | Dashbrd","metatags.collections.view.title":"{{name}} Collection | Dashbrd","metatags.collections.view.description":"Immerse yourself in the intricate details of {{name}} collection, featuring remarkable digital assets. Start your NFT journey today!","metatags.collections.view.image":"/images/meta/nft-collection.png","metatags.nfts.view.title":"{{nft}} NFT | Dashbrd","metatags.nfts.view.description":"Uncover the complete story of {{nft}} NFT from the {{collection}} collection, delving into its unique attributes and distinctive features.","metatags.nfts.view.image":"/images/meta/nft-details.png","metatags.settings.title":"Settings | Dashbrd","metatags.login.title":"Login | Dashbrd","metatags.privacy_policy.title":"Privacy Policy | Dashbrd","metatags.privacy_policy.description":"Dashbrd’s privacy policy outlines the information we collect and explains your choices surrounding how we use information about you.","metatags.terms_of_service.title":"Terms of Service | Dashbrd","metatags.terms_of_service.description":"These Terms of Service cover your use and access to services, products or websites of Dashbrd.","metatags.cookie_policy.title":"Cookie Policy | Dashbrd","metatags.cookie_policy.description":"Dashbrd uses cookies to make the website more user-friendly. Find out about the main types of cookies we use, and what we use them for.","metatags.welcome.title":"Welcome to Dashbrd | Web3 Simplified","pages.onboarding.title":"Get Started","pages.onboarding.heading":"Your monkeys were bored and ran off, we are trying to round them up.","pages.onboarding.message":"We are setting up your account. This process usually takes just a few minutes, but can take up to 15 minutes.","pages.error.heading":"Oops, something went wrong ...","pages.error.message":"Please try again or get in touch if the issue persists.","pages.maintenance.title":"Dashbrd is currently down for scheduled maintenance.","pages.maintenance.description":"We expect to be back soon. Thanks for your patience.","pages.dashboard.title":"My Wallet","pages.dashboard.breakdown.title":"Portfolio Breakdown","pages.dashboard.line_chart.data_error":"Could not load chart data","pages.collections.title":"Collections","pages.collections.collections":"Collections","pages.collections.collection_value":"Collection Value","pages.collections.nfts_owned":"NFTs Owned","pages.collections.header_title":"You own <0>{{nftsCount}} {{nfts}} across <0>{{collectionsCount}} {{collections}}, worth about <0><1>{{worth}}","pages.collections.search_placeholder":"Search by Collection","pages.collections.properties":"Properties","pages.collections.collections_network":"Collections Network","pages.collections.property_search_placeholder":"Feature Search","pages.collections.floor_price":"Floor Price","pages.collections.value":"Value","pages.collections.rarity":"Rarity","pages.collections.report":"Report","pages.collections.hide_collection":"Hide Collection","pages.collections.unhide_collection":"Unhide Collection","pages.collections.no_collections":"You do not own any NFTs yet. Once you do they will be shown here.","pages.collections.all_collections_hidden":"You have hidden all your collections. Unhide and they will appear here.","pages.collections.about_collection":"About Collection","pages.collections.show_hidden":"Show Hidden","pages.collections.show_my_collection":"Show My Collection","pages.collections.owned":"Owned","pages.collections.activities.loading_activities":"We're fetching Activity for this NFT, please hang tight, this can take a while.","pages.collections.activities.ignores_activities":"We don't support activity history for this collection yet.","pages.collections.activities.no_activity":"This collection does not have any activity yet.","pages.collections.activities.types.LABEL_MINT":"Mint","pages.collections.activities.types.LABEL_TRANSFER":"Transfer","pages.collections.activities.types.LABEL_SALE":"Sale","pages.collections.search.loading_results":"Loading results...","pages.collections.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.collections.search.no_results_with_filters":"We could not find anything matching your filters, please try again!","pages.collections.search.no_results_ownership":"You do not own any NFTs in this collection","pages.collections.search.error":"Could not load search results. Please try again later.","pages.collections.sorting.token_number":"Token Number","pages.collections.sorting.recently_received":"Recently Received","pages.collections.sorting.recently_created":"Recently Created","pages.collections.sorting.oldest_collection":"Oldest Collection","pages.collections.traits.description":"List of NFT traits by % of occurrence in the collection","pages.collections.traits.no_traits":"No Properties can be found for this NFT","pages.collections.menu.collection":"Collection","pages.collections.menu.activity":"Activity","pages.collections.hidden_modal.collection_hidden":"Collection Hidden","pages.collections.hidden_modal.description":"This collection is currently set to Hidden. Are you sure you want to unhide this collection? You can\n reset the collection to hidden from the collection menu.","pages.collections.hidden_modal.unhide":"Unhide","pages.collections.hidden_modal.error":"Something went wrong. Please try again.","pages.collections.external_modal.you_wish_continue":"You are about to leave Dashbrd to an external website. Dashbrd has no control over the content of\n this site. Are you sure you wish to continue?","pages.collections.external_modal.not_show":"Do not show this message again.","pages.collections.refresh.title":"Refresh your collection","pages.collections.refresh.notice":"You can refresh data every 15 minutes.","pages.collections.refresh.notice_wait":"Please wait. You can refresh data every 15 minutes.","pages.collections.refresh.toast":"We're updating information for your collection.","pages.nfts.nft":"nft","pages.nfts.about_nft":"About NFT","pages.nfts.owned_by":"Owned by","pages.nfts.collection_image":"collection image","pages.nfts.menu.properties":"Properties","pages.nfts.menu.activity":"Activity","pages.reports.title":"Submit a Report","pages.reports.description":"Thanks for looking out by reporting things that break the rules. Let us know what's happening and we'll receive the report.","pages.reports.success":"Thank you for your report. We'll review it and see if it breaks our ToS.","pages.reports.failed":"Something went wrong. Please try again.","pages.reports.throttle":"You have made too many requests. Please wait {{time}} before reporting again.","pages.reports.reported":"You have already reported this {{model}}.","pages.reports.reasons.spam":"Spam","pages.reports.reasons.violence":"Promoting Violence","pages.reports.reasons.hate":"Hate","pages.reports.reasons.inappropriate_content":"Inappropriate Content","pages.reports.reasons.impersonation":"Impersonation","pages.reports.reasons.trademark":"Trademark or Copyright","pages.reports.reasons.selfharm":"Self-Harm","pages.reports.reasons.harassment":"Harassment","pages.galleries.title":"Galleries","pages.galleries.empty_title":"No galleries have been published yet. Once they do they will appear here.","pages.galleries.search.loading_results":"Loading results...","pages.galleries.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.search.placeholder":"Search by Galleries","pages.galleries.search.placeholder_nfts":"Search by NFTs","pages.galleries.search.error":"Could not load search results. Please try again later.","pages.galleries.my_galleries.title":"My Galleries","pages.galleries.my_galleries.new_gallery":"New Gallery","pages.galleries.my_galleries.no_galleries":"You have not created any galleries yet. To create a gallery, click on the \"Create Gallery\" button.","pages.galleries.my_galleries.succesfully_deleted":"Gallery successfully deleted","pages.galleries.my_galleries.successfully_created":"Gallery has been successfully created","pages.galleries.my_galleries.successfully_updated":"Gallery has been successfully updated","pages.galleries.my_galleries.new_gallery_no_nfts":"Creating a Gallery requires you to own an NFT.","pages.galleries.copy_gallery_link":"Copy Gallery Link","pages.galleries.my_nfts":"My NFTs","pages.galleries.value":"Value","pages.galleries.floor_price":"Floor Price","pages.galleries.nfts":"NFTs","pages.galleries.collections":"Collections","pages.galleries.galleries_count_simple":"{0} galleries|{1} gallery|[2,*] galleries","pages.galleries.galleries_count":"{0} {{count}} Galleries|{1} {{count}} Gallery|[2,*] {{count}} Galleries","pages.galleries.collections_count_simple":"{0} collections|{1} collection|[2,*] collections","pages.galleries.collections_count":"{0} {{count}} Collections|{1} {{count}} Collection|[2,*] {{count}} Collections","pages.galleries.nfts_count_simple":"{0} NFTs|{1} NFT|[2,*] NFTs","pages.galleries.nfts_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","pages.galleries.users_count_simple":"{0} users|{1} user|[2,*] users","pages.galleries.users_count":"{0} {{count}} Users|{1} {{count}} User|[2,*] {{count}} Users","pages.galleries.featuring":"Featuring","pages.galleries.curated_by":"Curated by","pages.galleries.worth_about":"Worth About","pages.galleries.valued_at":"valued at","pages.galleries.from":"From","pages.galleries.most_popular_galleries":"Most Popular Galleries","pages.galleries.newest_galleries":"Newest Galleries","pages.galleries.most_valuable_galleries":"Most Valuable Galleries","pages.galleries.most_popular":"Most Popular","pages.galleries.newest":"Newest","pages.galleries.most_valuable":"Most Valuable","pages.galleries.create.search_by_nfts":"Search by NFTs","pages.galleries.create.input_placeholder":"Enter gallery name","pages.galleries.create.title_too_long":"Gallery name must not exceed {{max}} characters.","pages.galleries.create.already_selected_nft":"NFT already exists in this gallery","pages.galleries.create.nft_missing_image":"Only NFTs with images can be added to galleries","pages.galleries.create.gallery_cover":"Gallery Cover","pages.galleries.create.gallery_cover_description":"The cover is used for the card on the gallery list page. While the cover is not a requirement it will allow you to add personality and stand out from the crowd.","pages.galleries.create.gallery_cover_information":"Image dimensions must be at least 287px x 190px, with a max size of 2 MB (JPG, PNG or GIF)","pages.galleries.create.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.create.templates.cover":"Cover","pages.galleries.create.templates.template":"Template","pages.galleries.create.templates.select":"Select Gallery Template","pages.galleries.create.templates.basic":"Basic Gallery","pages.galleries.create.templates.coming_soon":"More Coming Soon","pages.galleries.create.load_more_collections_one":"Load {{count}} More Collection","pages.galleries.create.load_more_collections_other":"Load {{count}} More Collections","pages.galleries.create.load_more_nfts":"Load More NFTs","pages.galleries.create.can_purchase":"You can purchase NFTs with these top NFT Marketplaces:","pages.galleries.create.must_own_one_nft":"You must own at least one (1) NFT in order to create a gallery.","pages.galleries.create.back_to_galleries":"Back to Galleries","pages.galleries.create.draft_saved":"Draft Saved","pages.galleries.create.saving_to_draft":"Saving to draft","pages.galleries.delete_modal.title":"Delete Gallery","pages.galleries.delete_modal.confirmation_text":"Are you sure you want to delete the gallery? Everything you've done will be deleted and you won't be able to get it back.","pages.galleries.consists_of_collections":"{0} This gallery consists of {{count}} collections|{1} This gallery consists of {{count}} collection|[2,*] This gallery consists of {{count}} collections","pages.galleries.guest_banner.title":"Craft the ultimate","pages.galleries.guest_banner.subtitle":"Pick your favorites, curate your gallery, & share it with the world.","pages.profile.title":"Profile","pages.token_panel.balance_tooltip":"Total percentage of the portfolio held in this token","pages.token_panel.insufficient_funds":"Insufficient Balance","pages.token_panel.error":"Dashbrd has failed to load token information. Please try again later.","pages.token_panel.failed_to_retrieve_transactions":"We were unable to fetch your transactions.","pages.token_panel.tabs.transaction_history":"Transaction History","pages.token_panel.tabs.history":"History","pages.token_panel.tabs.market_data":"Market Data","pages.token_panel.details.current_price":"Current Price","pages.token_panel.details.title":"Token Details","pages.token_panel.details.market_cap":"Market Cap","pages.token_panel.details.volume":"Daily Volume","pages.token_panel.details.supply":"Minted Supply","pages.token_panel.details.ath":"All-Time High","pages.token_panel.details.atl":"All-Time Low","pages.token_panel.chart.failed":"Dashbrd has failed to load chart information. Please try again later.","pages.transaction_details_panel.title":"Transaction Details","pages.transaction_details_panel.details.blockchain":"Blockchain","pages.transaction_details_panel.details.timestamp":"Timestamp","pages.transaction_details_panel.details.transaction_hash":"Transaction Hash","pages.transaction_details_panel.details.transaction_fee":"Transaction Fee","pages.transaction_details_panel.details.gas_price":"Gas Price","pages.transaction_details_panel.details.gas_used":"Gas Used","pages.transaction_details_panel.details.nonce":"Nonce","pages.send_receive_panel.send.labels.token_and_amount":"Token and Amount","pages.send_receive_panel.send.labels.destination_address":"Destination Address","pages.send_receive_panel.send.labels.projected_fee":"Projected Fee","pages.send_receive_panel.send.placeholders.enter_amount":"Enter Amount","pages.send_receive_panel.send.placeholders.insert_recipient_address":"Insert Recipient Address","pages.send_receive_panel.send.placeholders.projected_fee":"Projected Fee","pages.send_receive_panel.send.errors.amount":"Insufficient Funds: You do not have enough to cover the amount + fee.","pages.send_receive_panel.send.errors.destination":"Destination address is not correct. Check and input again.","pages.send_receive_panel.send.hints.token_price":"Token Price","pages.send_receive_panel.send.fees.Fast":"Fast","pages.send_receive_panel.send.fees.Avg":"Avg","pages.send_receive_panel.send.fees.Slow":"Slow","pages.send_receive_panel.send.search_dropdown.placeholder":"Search token","pages.send_receive_panel.send.search_dropdown.no_results":"No results","pages.send_receive_panel.send.search_dropdown.error":"Error occurred while searching tokens.","pages.send_receive_panel.send.transaction_time":"Transaction Time: ~{{ time }} minutes","pages.send_receive_panel.send.from":"From","pages.send_receive_panel.send.to":"To","pages.send_receive_panel.send.amount":"Amount","pages.send_receive_panel.send.fee":"Fee","pages.send_receive_panel.send.total_amount":"Total Amount","pages.send_receive_panel.send.waiting_message":"Review and verify the information on your MetaMask. Sign to send the transaction.","pages.send_receive_panel.send.waiting_spinner_text":"Waiting for confirmation...","pages.send_receive_panel.send.failed_message":"It looks like something went wrong while sending your transaction. Press 'Retry' to make another attempt.","pages.send_receive_panel.receive.alert":"Send only Polygon or Ethereum Network compatible tokens to this address or you could permanently lose your funds!","pages.settings.title":"Settings","pages.settings.sidebar.general":"General","pages.settings.sidebar.notifications":"Notifications","pages.settings.sidebar.session_history":"Sessions History","pages.settings.general.title":"Settings","pages.settings.general.subtitle":"Customize your App Experience","pages.settings.general.currency":"Currency","pages.settings.general.currency_subtitle":"Select your default currency which will be used throughout the app.","pages.settings.general.time_date":"Time & Date","pages.settings.general.time_date_subtitle":"Select how you want time and date be shown inside app.","pages.settings.general.date_format":"Date Format","pages.settings.general.time_format":"Time Format","pages.settings.general.timezone":"Timezone","pages.settings.general.set_defaults":"Set Defaults","pages.settings.general.set_defaults_content":"Reverting to the default settings will remove any customizations previously made. Are you sure?","pages.settings.general.save":"Save Settings","pages.settings.general.saved":"Your settings have been successfully saved","pages.wallet.title":"Wallet","pages.privacy_policy.title":"Privacy Policy","pages.terms_of_service.title":"Terms of Service","pagination.previous":"« Previous","pagination.next":"Next »","passwords.reset":"Your password has been reset!","passwords.sent":"We have emailed your password reset link!","passwords.throttled":"Please wait before retrying.","passwords.token":"This password reset token is invalid.","passwords.user":"We can't find a user with that email address.","urls.landing":"https://dashbrd.com","urls.cookie_policy":"https://dashbrd.com/cookie-policy","urls.privacy_policy":"https://dashbrd.com/privacy-policy","urls.terms_of_service":"https://dashbrd.com/terms-of-service","urls.twitter":"https://x.com/DashbrdApp","urls.discord":"https://discord.gg/MJyWKkCJ5k","urls.github":"https://github.com/ArdentHQ/dashbrd","urls.coingecko":"https://www.coingecko.com","urls.etherscan":"https://etherscan.io","urls.polygonscan":"https://polygonscan.com","urls.alchemy":"https://www.alchemy.com","urls.moralis":"https://moralis.io","urls.mnemonic":"https://www.mnemonichq.com","urls.opensea":"https://opensea.io/","urls.explorers.etherscan.token_transactions":"https://etherscan.io/token/{{token}}?a={{address}}","urls.explorers.etherscan.addresses":"https://etherscan.io/address/{{address}}","urls.explorers.etherscan.transactions":"https://etherscan.io/tx/{{id}}","urls.explorers.etherscan.nft":"https://etherscan.io/nft/{{address}}/{{nftId}}","urls.explorers.polygonscan.token_transactions":"https://polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.polygonscan.addresses":"https://polygonscan.com/address/{{address}}","urls.explorers.polygonscan.transactions":"https://polygonscan.com/tx/{{id}}","urls.explorers.polygonscan.nft":"https://polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.mumbai.token_transactions":"https://mumbai.polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.mumbai.addresses":"https://mumbai.polygonscan.com/address/{{address}}","urls.explorers.mumbai.transactions":"https://mumbai.polygonscan.com/tx/{{id}}","urls.explorers.mumbai.nft":"https://mumbai.polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.goerli.token_transactions":"https://goerli.etherscan.io/token/{{token}}?a={{address}}","urls.explorers.goerli.addresses":"https://goerli.etherscan.io/address/{{address}}","urls.explorers.goerli.transactions":"https://goerli.etherscan.io/tx/{{id}}","urls.explorers.goerli.nft":"https://goerli.etherscan.io/nft/{{address}}/{{nftId}}","urls.marketplaces.opensea.collection":"https://opensea.io/assets/{{network}}/{{address}}","urls.marketplaces.opensea.nft":"https://opensea.io/assets/{{network}}/{{address}}/{{nftId}}","urls.marketplaces.rarible.collection":"https://rarible.com/collection/{{address}}/items","urls.marketplaces.rarible.nft":"https://rarible.com/token/{{address}}:{{nftId}}","urls.marketplaces.blur.collection":"https://blur.io/collection/{{address}}","urls.marketplaces.blur.nft":"https://blur.io/asset/{{address}}/{{nftId}}","urls.marketplaces.looksrare.collection":"https://looksrare.org/collections/{{address}}","urls.marketplaces.looksrare.nft":"https://looksrare.org/collections/{{address}}/{{nftId}}","validation.accepted":"The {{attribute}} must be accepted.","validation.accepted_if":"The {{attribute}} must be accepted when {{other}} is {{value}}.","validation.active_url":"The {{attribute}} is not a valid URL.","validation.after":"The {{attribute}} must be a date after {{date}}.","validation.after_or_equal":"The {{attribute}} must be a date after or equal to {{date}}.","validation.alpha":"The {{attribute}} must only contain letters.","validation.alpha_dash":"The {{attribute}} must only contain letters, numbers, dashes and underscores.","validation.alpha_num":"The {{attribute}} must only contain letters and numbers.","validation.array":"The {{attribute}} must be an array.","validation.ascii":"The {{attribute}} must only contain single-byte alphanumeric characters and symbols.","validation.before":"The {{attribute}} must be a date before {{date}}.","validation.before_or_equal":"The {{attribute}} must be a date before or equal to {{date}}.","validation.between.array":"The {{attribute}} must have between {{min}} and {{max}} items.","validation.between.file":"The {{attribute}} must be between {{min}} and {{max}} kilobytes.","validation.between.numeric":"The {{attribute}} must be between {{min}} and {{max}}.","validation.between.string":"The {{attribute}} must be between {{min}} and {{max}} characters.","validation.boolean":"The {{attribute}} field must be true or false.","validation.confirmed":"The {{attribute}} confirmation does not match.","validation.current_password":"The password is incorrect.","validation.date":"The {{attribute}} is not a valid date.","validation.date_equals":"The {{attribute}} must be a date equal to {{date}}.","validation.date_format":"The {{attribute}} does not match the format {{format}}.","validation.decimal":"The {{attribute}} must have {{decimal}} decimal places.","validation.declined":"The {{attribute}} must be declined.","validation.declined_if":"The {{attribute}} must be declined when {{other}} is {{value}}.","validation.different":"The {{attribute}} and {{other}} must be different.","validation.digits":"The {{attribute}} must be {{digits}} digits.","validation.digits_between":"The {{attribute}} must be between {{min}} and {{max}} digits.","validation.dimensions":"The {{attribute}} has invalid image dimensions.","validation.distinct":"The {{attribute}} field has a duplicate value.","validation.doesnt_end_with":"The {{attribute}} may not end with one of the following: {{values}}.","validation.doesnt_start_with":"The {{attribute}} may not start with one of the following: {{values}}.","validation.email":"The {{attribute}} must be a valid email address.","validation.ends_with":"The {{attribute}} must end with one of the following: {{values}}.","validation.enum":"The selected {{attribute}} is invalid.","validation.exists":"The selected {{attribute}} is invalid.","validation.file":"The {{attribute}} must be a file.","validation.filled":"The {{attribute}} field must have a value.","validation.gt.array":"The {{attribute}} must have more than {{value}} items.","validation.gt.file":"The {{attribute}} must be greater than {{value}} kilobytes.","validation.gt.numeric":"The {{attribute}} must be greater than {{value}}.","validation.gt.string":"The {{attribute}} must be greater than {{value}} characters.","validation.gte.array":"The {{attribute}} must have {{value}} items or more.","validation.gte.file":"The {{attribute}} must be greater than or equal to {{value}} kilobytes.","validation.gte.numeric":"The {{attribute}} must be greater than or equal to {{value}}.","validation.gte.string":"The {{attribute}} must be greater than or equal to {{value}} characters.","validation.image":"The {{attribute}} must be an image.","validation.in":"The selected {{attribute}} is invalid.","validation.in_array":"The {{attribute}} field does not exist in {{other}}.","validation.integer":"The {{attribute}} must be an integer.","validation.ip":"The {{attribute}} must be a valid IP address.","validation.ipv4":"The {{attribute}} must be a valid IPv4 address.","validation.ipv6":"The {{attribute}} must be a valid IPv6 address.","validation.json":"The {{attribute}} must be a valid JSON string.","validation.lowercase":"The {{attribute}} must be lowercase.","validation.lt.array":"The {{attribute}} must have less than {{value}} items.","validation.lt.file":"The {{attribute}} must be less than {{value}} kilobytes.","validation.lt.numeric":"The {{attribute}} must be less than {{value}}.","validation.lt.string":"The {{attribute}} must be less than {{value}} characters.","validation.lte.array":"The {{attribute}} must not have more than {{value}} items.","validation.lte.file":"The {{attribute}} must be less than or equal to {{value}} kilobytes.","validation.lte.numeric":"The {{attribute}} must be less than or equal to {{value}}.","validation.lte.string":"The {{attribute}} must be less than or equal to {{value}} characters.","validation.mac_address":"The {{attribute}} must be a valid MAC address.","validation.max.array":"The {{attribute}} must not have more than {{max}} items.","validation.max.file":"The {{attribute}} must not be greater than {{max}} kilobytes.","validation.max.numeric":"The {{attribute}} must not be greater than {{max}}.","validation.max.string":"The {{attribute}} must not be greater than {{max}} characters.","validation.max_digits":"The {{attribute}} must not have more than {{max}} digits.","validation.mimes":"The {{attribute}} must be a file of type: {{values}}.","validation.mimetypes":"The {{attribute}} must be a file of type: {{values}}.","validation.min.array":"The {{attribute}} must have at least {{min}} items.","validation.min.file":"The {{attribute}} must be at least {{min}} kilobytes.","validation.min.numeric":"The {{attribute}} must be at least {{min}}.","validation.min.string":"The {{attribute}} must be at least {{min}} characters.","validation.min_digits":"The {{attribute}} must have at least {{min}} digits.","validation.missing":"The {{attribute}} field must be missing.","validation.missing_if":"The {{attribute}} field must be missing when {{other}} is {{value}}.","validation.missing_unless":"The {{attribute}} field must be missing unless {{other}} is {{value}}.","validation.missing_with":"The {{attribute}} field must be missing when {{values}} is present.","validation.missing_with_all":"The {{attribute}} field must be missing when {{values}} are present.","validation.multiple_of":"The {{attribute}} must be a multiple of {{value}}.","validation.not_in":"The selected {{attribute}} is invalid.","validation.not_regex":"The {{attribute}} format is invalid.","validation.numeric":"The {{attribute}} must be a number.","validation.password.letters":"The {{attribute}} must contain at least one letter.","validation.password.mixed":"The {{attribute}} must contain at least one uppercase and one lowercase letter.","validation.password.numbers":"The {{attribute}} must contain at least one number.","validation.password.symbols":"The {{attribute}} must contain at least one symbol.","validation.password.uncompromised":"The given {{attribute}} has appeared in a data leak. Please choose a different {{attribute}}.","validation.present":"The {{attribute}} field must be present.","validation.prohibited":"The {{attribute}} field is prohibited.","validation.prohibited_if":"The {{attribute}} field is prohibited when {{other}} is {{value}}.","validation.prohibited_unless":"The {{attribute}} field is prohibited unless {{other}} is in {{values}}.","validation.prohibits":"The {{attribute}} field prohibits {{other}} from being present.","validation.regex":"The {{attribute}} format is invalid.","validation.required":"The {{attribute}} field is required.","validation.required_array_keys":"The {{attribute}} field must contain entries for: {{values}}.","validation.required_if":"The {{attribute}} field is required when {{other}} is {{value}}.","validation.required_if_accepted":"The {{attribute}} field is required when {{other}} is accepted.","validation.required_unless":"The {{attribute}} field is required unless {{other}} is in {{values}}.","validation.required_with":"The {{attribute}} field is required when {{values}} is present.","validation.required_with_all":"The {{attribute}} field is required when {{values}} are present.","validation.required_without":"The {{attribute}} field is required when {{values}} is not present.","validation.required_without_all":"The {{attribute}} field is required when none of {{values}} are present.","validation.same":"The {{attribute}} and {{other}} must match.","validation.size.array":"The {{attribute}} must contain {{size}} items.","validation.size.file":"The {{attribute}} must be {{size}} kilobytes.","validation.size.numeric":"The {{attribute}} must be {{size}}.","validation.size.string":"The {{attribute}} must be {{size}} characters.","validation.starts_with":"The {{attribute}} must start with one of the following: {{values}}.","validation.string":"The {{attribute}} must be a string.","validation.timezone":"The {{attribute}} must be a valid timezone.","validation.unique":"The {{attribute}} has already been taken.","validation.uploaded":"The {{attribute}} failed to upload.","validation.uppercase":"The {{attribute}} must be uppercase.","validation.url":"The {{attribute}} must be a valid URL.","validation.ulid":"The {{attribute}} must be a valid ULID.","validation.uuid":"The {{attribute}} must be a valid UUID.","validation.custom.attribute-name.rule-name":"custom-message","validation.unsupported_currency_code":"The currency code you provided is invalid or not supported.","validation.unsupported_period":"The period you provided is invalid or not supported.","validation.unsupported_token_symbol":"The token symbol you provided is invalid or not supported.","validation.gallery_title_required":"Gallery name is required.","validation.gallery_title_max_characters":"The gallery name should not exceed 50 characters.","validation.gallery_title_invalid":"The gallery name is invalid.","validation.nfts_required":"Please add at least one NFT.","validation.nfts_max_size":"Galleries can contain no more than {{limit}} NFTs","validation.invalid_nfts":"The NFT in position {{position}} is invalid, please select another one.","validation.invalid_cover":"You have selected an invalid cover image, please try another one."} \ No newline at end of file diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts index ef6ea78cb..4a8552586 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -112,6 +112,20 @@ describe("useGalleryDrafts", () => { }); }); + it("should not create a new row if disabled", async () => { + mocks.useIndexedDB().add.mockResolvedValue(2); + + const { result } = renderHook(() => useGalleryDrafts(undefined, true)); + + act(() => { + result.current.setDraftTitle("hello"); + }); + + await waitFor(() => { + expect(result.current.draft.id).toBe(null); + }); + }); + it("should try to update the row if draft is present", async () => { const givenDraftId = 2; const updateMock = vi.fn(); diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts index dd11f9956..b318a1a3c 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts @@ -125,7 +125,7 @@ export const useGalleryDrafts = (givenDraftId?: number, disabled?: boolean): Gal }; const deleteDraft = async (): Promise => { - if (disabled === true || draft.id === null) return; + if (draft.id === null) return; await database.deleteRecord(draft.id); setReachedLimit(false); From c66fb97388a4de576de5a666d471ac0a106986a5 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 17:19:56 +0400 Subject: [PATCH 31/35] fix tests --- resources/js/I18n/Locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/I18n/Locales/en.json b/resources/js/I18n/Locales/en.json index 0327e7a45..fc2ff1081 100644 --- a/resources/js/I18n/Locales/en.json +++ b/resources/js/I18n/Locales/en.json @@ -1 +1 @@ -{"auth.welcome":"Welcome to Dashbrd","auth.logged_in":"You're logged in!","auth.log_out":"Log out","auth.failed":"These credentials do not match our records.","auth.session_timeout":"Your session has timed out. Please refresh the page and try connecting your account again.","auth.session_timeout_modal":"Seems like your session has timed out. Please connnect your wallet again.","auth.password":"The provided password is incorrect.","auth.throttle":"Too many login attempts. Please try again in {{seconds}} seconds.","auth.wallet.connecting":"Connecting …","auth.wallet.waiting_for_signature":"Waiting for Signature …","auth.wallet.switching_wallet":"Switching Wallet …","auth.wallet.connect":"Connect Wallet","auth.wallet.connect_long":"Connect Your Wallet to Get Started","auth.wallet.sign_subtitle":"Connect Your Wallet to Continue","auth.wallet.disconnect":"Disconnect Wallet","auth.wallet.install":"Install MetaMask","auth.wallet.install_long":"Install MetaMask to Get Started","auth.wallet.sign":"Sign Message","auth.wallet.sign_message":"Welcome to Dashbrd. In order to login, sign this message with your wallet. It doesn't cost you anything!\n\nSigning ID (you can ignore this): {{nonce}}","auth.wallet.connect_subtitle":"Click on the MetaMask icon in your browser to confirm the action and connect your wallet.","auth.wallet.requires_signature":"In order to prevent impersonation, we require a signature to perform this action. This signature is only a signed message and does not give any access to your wallet.","auth.errors.metamask.no_account":"No account found. Please connect your wallet and try again.","auth.errors.metamask.generic":"Connection attempt error. Please retry and follow the steps to connect your wallet.","auth.errors.metamask.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","auth.errors.metamask.provider_missing":"You don't have MetaMask installed in your browser. Please install and try again.","auth.errors.metamask.user_rejected":"It looks like you cancelled signing of the authentication message. Please try again.","auth.errors.metamask.provider_not_set":"Ethereum provider is not set","auth.validation.wallet_login_failed":"There was a problem trying to verify your signature. Please try again.","auth.validation.invalid_address":"Your wallet address is invalid. Please try again.","auth.validation.invalid_signature":"Signature is invalid. Please try again.","auth.validation.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","common.add":"Add","common.amount":"Amount","common.balance":"Balance","common.cancel":"Cancel","common.delete":"Delete","common.edit":"Edit","common.confirm":"Confirm","common.connect":"Connect","common.continue":"Continue","common.done":"Done","common.filter":"Filter","common.items":"Items","common.receive":"Receive","common.received":"Received","common.retry":"Retry","common.records":"Records","common.save":"Save","common.send":"Send","common.sent":"Sent","common.show":"Show","common.searching":"Searching...","common.other":"Other","common.owned":"Owned","common.token":"Token","common.tokens":"Tokens","common.wallet":"Wallet","common.pending":"Pending","common.publish":"Publish","common.empty":"Empty","common.your_address":"Your Address","common.warning":"Warning","common.my_address":"My Address","common.my_wallet":"My Wallet","common.my_balance":"My Balance","common.na":"N/A","common.simple_plural_without_data":"One comment|Many comments","common.simple_plural_with_data":"{{count}} comment|{{count}} comments","common.advanced_plural_without_data":"{0} no comments yet|{1} 1 comment|[2,*] Many comments","common.advanced_plural_with_data":"{0} no comments yet|{1} 1 comment|[2,*] {{count}} comments","common.copy_clipboard":"Copy to Clipboard","common.copy":"Copy","common.download":"Download","common.zoom":"Zoom","common.my_collection":"My Collection","common.max":"Max","common.chain":"Chain","common.copied":"Copied!","common.coming_soon":"Coming Soon","common.more_details":"More Details","common.close":"Close","common.close_toast":"Close toast","common.loading":"Loading","common.price":"Price","common.market_cap":"Market Cap","common.volume":"Volume {{frequency}}","common.value":"Value","common.last_n_days":"Last {{count}} Days","common.details":"Details","common.view_all":"View All","common.view_more_on_polygonscan":"View More on Polygonscan","common.view_more_on_etherscan":"View More on Etherscan","common.view_nft_on_etherscan":"View NFT on Etherscan","common.view_nft_on_polygonscan":"View NFT on Polygonscan","common.view_nft_on_goerli_tesnet":"View NFT on Goerli Testnet Explorer","common.view_nft_on_mumbai_tesnet":"View NFT on Mumbai Testnet Explorer","common.polygonscan":"Polygonscan","common.etherscan":"Etherscan","common.featured":"Featured","common.floor_price":"Floor Price","common.floor":"Floor","common.supply":"Supply","common.owners":"Owners","common.created":"Created","common.nft":"NFT","common.collection":"Collection","common.collections":"Collections","common.gallery":"Gallery","common.basic_gallery":"Basic Gallery","common.gallery_name":"Gallery Name","common.create_gallery":"Create Gallery","common.external_link":"External Link","common.follow_link":"Follow Link","common.cover":"Cover","common.template":"Template","common.page":"Page","common.polygon":"Polygon","common.ethereum":"Ethereum","common.mumbai":"Mumbai","common.goerli":"Goerli","common.of":"of","common.pagination_input_placeholder":"Enter the page number","common.pagination_input_placeholder_mobile":"Page number","common.nft_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","common.nft_gallery":"NFT Gallery","common.unable_to_retrieve_image":"Unable to retrieve your NFT image at this time","common.optional":"Optional","common.selected":"Selected","common.select":"Select","common.preview":"Preview","common.image_size_error":"Image size must not exceed 2MB","common.image_dimensions_error":"Image dimensions must exceed 287px x 190px","common.write_to_confirm":"Write {{word}} to confirm","common.n_hours":"{0} {{count}} hour|{1} {{count}} hour|[2,*] {{count}} hours","common.n_minutes":"{0} {{count}} minute|{1} {{count}} minute|[2,*] {{count}} minutes","common.report":"Report","common.n_nfts":"{0}NFTs|{1}NFT|[2,*]NFTs","common.n_collections":"{0}collections|{1}collection|[2,*]collections","common.back":"Back","common.back_to":"Back to","common.name":"Name","common.type":"Type","common.from":"From","common.to":"To","common.sale_price":"Sale Price","common.recent_activity":"Recent Activity","common.time":"Time","common.datetime.few_seconds_ago":"A few seconds ago","common.datetime.minutes_ago":"{0} Less than a minute ago|{1} A minute ago|[2,*] {{count}} minutes ago","common.datetime.hours_ago":"{0} Less than an hour ago|{1} An hour ago|[2,*] {{count}} hours ago","common.datetime.days_ago":"{0} Less than a day ago|{1} A day ago|[2,*] {{count}} days ago","common.datetime.weeks_ago":"{0} Less than a week ago|{1} A week ago|[2,*] {{count}} weeks ago","common.datetime.months_ago":"{0} Less than a month ago|{1} A month ago|[2,*] {{count}} months ago","common.datetime.years_ago":"{0} Less than a year ago|{1} A year ago|[2,*] {{count}} years ago","common.no_traits_found":"No traits found.","common.empty_transactions":"You don't have any transactions yet. Once transactions have been made, they will show up here.","common.error":"Error","common.refresh_metadata":"Refresh Metadata","common.refreshing_metadata":"Refreshing Metadata... Please check back later.","common.pending_confirmation":"Pending Confirmation","common.confirmed_transaction":"Confirmed Transaction","common.transaction_error":"Transaction Error","common.transaction_error_description_first_part":"Your transaction encountered an error. View this transaction on","common.transaction_error_description_second_part":"for more details.","common.home":"Home","common.contact":"Contact","common.menu":"Menu","common.website":"Website","common.twitter":"Twitter","common.discord":"Discord","common.sort":"Sort","common.published":"Published","common.drafts":"Drafts","footer.copyright":"{{year}} © Dashbrd. All rights reserved.","footer.all_rights_reserved":"All rights reserved","footer.privacy_policy":"Privacy Policy","footer.terms_of_service":"Terms of Service","footer.powered_by":"Powered by","format.fiat":"{{ value, currency }}","format.number":"{{ value, number }}","metatags.home.title":"Dashbrd | Web3 Portfolio Management Made Simple","metatags.home.description":"Simplify your Web3 journey with Dashbrd. Manage your portfolio of tokens, NFTs, and other digital collectibles across the Ethereum and Polygon blockchains.","metatags.home.image":"/images/meta/home.png","metatags.error.title":"Error {{code}} | Dashbrd","metatags.wallet.title":"My Wallet | Dashbrd","metatags.galleries.title":"Top NFT Galleries | Dashbrd","metatags.galleries.image":"/images/meta/nft-galleries.png","metatags.galleries.description":"Explore user published NFT galleries to find custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.most_popular.title":"Most Popular Galleries | Dashbrd","metatags.galleries.most_popular.image":"/images/meta/most-popular-nft-galleries.png","metatags.galleries.most_popular.description":"Explore and discover Most Popular NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.newest.title":"Newest Galleries | Dashbrd","metatags.galleries.newest.image":"/images/meta/newest-nft-galleries.png","metatags.galleries.newest.description":"Explore and discover most recent NFT galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More","metatags.galleries.most_valuable.title":"Most Valuable Galleries | Dashbrd","metatags.galleries.most_valuable.image":"/images/meta/most-valuable-nft-galleries.png","metatags.galleries.most_valuable.description":"Explore and discover Most Valuable NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.view.title":"{{name}} | Dashbrd","metatags.galleries.view.description":"{{name}} | A Curated NFT Gallery at Dashbrd","metatags.galleries.view.image":"/images/meta/nft-gallery.png","metatags.my_galleries.title":"My Galleries | Dashbrd","metatags.my_galleries.title_draft":"My Draft Galleries | Dashbrd","metatags.my_galleries.create.title":"Create Gallery | Dashbrd","metatags.my_galleries.edit.title":"Edit {{name}} | Dashbrd","metatags.collections.title":"Collections | Dashbrd","metatags.collections.view.title":"{{name}} Collection | Dashbrd","metatags.collections.view.description":"Immerse yourself in the intricate details of {{name}} collection, featuring remarkable digital assets. Start your NFT journey today!","metatags.collections.view.image":"/images/meta/nft-collection.png","metatags.nfts.view.title":"{{nft}} NFT | Dashbrd","metatags.nfts.view.description":"Uncover the complete story of {{nft}} NFT from the {{collection}} collection, delving into its unique attributes and distinctive features.","metatags.nfts.view.image":"/images/meta/nft-details.png","metatags.settings.title":"Settings | Dashbrd","metatags.login.title":"Login | Dashbrd","metatags.privacy_policy.title":"Privacy Policy | Dashbrd","metatags.privacy_policy.description":"Dashbrd’s privacy policy outlines the information we collect and explains your choices surrounding how we use information about you.","metatags.terms_of_service.title":"Terms of Service | Dashbrd","metatags.terms_of_service.description":"These Terms of Service cover your use and access to services, products or websites of Dashbrd.","metatags.cookie_policy.title":"Cookie Policy | Dashbrd","metatags.cookie_policy.description":"Dashbrd uses cookies to make the website more user-friendly. Find out about the main types of cookies we use, and what we use them for.","metatags.welcome.title":"Welcome to Dashbrd | Web3 Simplified","pages.onboarding.title":"Get Started","pages.onboarding.heading":"Your monkeys were bored and ran off, we are trying to round them up.","pages.onboarding.message":"We are setting up your account. This process usually takes just a few minutes, but can take up to 15 minutes.","pages.error.heading":"Oops, something went wrong ...","pages.error.message":"Please try again or get in touch if the issue persists.","pages.maintenance.title":"Dashbrd is currently down for scheduled maintenance.","pages.maintenance.description":"We expect to be back soon. Thanks for your patience.","pages.dashboard.title":"My Wallet","pages.dashboard.breakdown.title":"Portfolio Breakdown","pages.dashboard.line_chart.data_error":"Could not load chart data","pages.collections.title":"Collections","pages.collections.collections":"Collections","pages.collections.collection_value":"Collection Value","pages.collections.nfts_owned":"NFTs Owned","pages.collections.header_title":"You own <0>{{nftsCount}} {{nfts}} across <0>{{collectionsCount}} {{collections}}, worth about <0><1>{{worth}}","pages.collections.search_placeholder":"Search by Collection","pages.collections.properties":"Properties","pages.collections.collections_network":"Collections Network","pages.collections.property_search_placeholder":"Feature Search","pages.collections.floor_price":"Floor Price","pages.collections.value":"Value","pages.collections.rarity":"Rarity","pages.collections.report":"Report","pages.collections.hide_collection":"Hide Collection","pages.collections.unhide_collection":"Unhide Collection","pages.collections.no_collections":"You do not own any NFTs yet. Once you do they will be shown here.","pages.collections.all_collections_hidden":"You have hidden all your collections. Unhide and they will appear here.","pages.collections.about_collection":"About Collection","pages.collections.show_hidden":"Show Hidden","pages.collections.show_my_collection":"Show My Collection","pages.collections.owned":"Owned","pages.collections.activities.loading_activities":"We're fetching Activity for this NFT, please hang tight, this can take a while.","pages.collections.activities.ignores_activities":"We don't support activity history for this collection yet.","pages.collections.activities.no_activity":"This collection does not have any activity yet.","pages.collections.activities.types.LABEL_MINT":"Mint","pages.collections.activities.types.LABEL_TRANSFER":"Transfer","pages.collections.activities.types.LABEL_SALE":"Sale","pages.collections.search.loading_results":"Loading results...","pages.collections.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.collections.search.no_results_with_filters":"We could not find anything matching your filters, please try again!","pages.collections.search.no_results_ownership":"You do not own any NFTs in this collection","pages.collections.search.error":"Could not load search results. Please try again later.","pages.collections.sorting.token_number":"Token Number","pages.collections.sorting.recently_received":"Recently Received","pages.collections.sorting.recently_created":"Recently Created","pages.collections.sorting.oldest_collection":"Oldest Collection","pages.collections.traits.description":"List of NFT traits by % of occurrence in the collection","pages.collections.traits.no_traits":"No Properties can be found for this NFT","pages.collections.menu.collection":"Collection","pages.collections.menu.activity":"Activity","pages.collections.hidden_modal.collection_hidden":"Collection Hidden","pages.collections.hidden_modal.description":"This collection is currently set to Hidden. Are you sure you want to unhide this collection? You can\n reset the collection to hidden from the collection menu.","pages.collections.hidden_modal.unhide":"Unhide","pages.collections.hidden_modal.error":"Something went wrong. Please try again.","pages.collections.external_modal.you_wish_continue":"You are about to leave Dashbrd to an external website. Dashbrd has no control over the content of\n this site. Are you sure you wish to continue?","pages.collections.external_modal.not_show":"Do not show this message again.","pages.collections.refresh.title":"Refresh your collection","pages.collections.refresh.notice":"You can refresh data every 15 minutes.","pages.collections.refresh.notice_wait":"Please wait. You can refresh data every 15 minutes.","pages.collections.refresh.toast":"We're updating information for your collection.","pages.nfts.nft":"nft","pages.nfts.about_nft":"About NFT","pages.nfts.owned_by":"Owned by","pages.nfts.collection_image":"collection image","pages.nfts.menu.properties":"Properties","pages.nfts.menu.activity":"Activity","pages.reports.title":"Submit a Report","pages.reports.description":"Thanks for looking out by reporting things that break the rules. Let us know what's happening and we'll receive the report.","pages.reports.success":"Thank you for your report. We'll review it and see if it breaks our ToS.","pages.reports.failed":"Something went wrong. Please try again.","pages.reports.throttle":"You have made too many requests. Please wait {{time}} before reporting again.","pages.reports.reported":"You have already reported this {{model}}.","pages.reports.reasons.spam":"Spam","pages.reports.reasons.violence":"Promoting Violence","pages.reports.reasons.hate":"Hate","pages.reports.reasons.inappropriate_content":"Inappropriate Content","pages.reports.reasons.impersonation":"Impersonation","pages.reports.reasons.trademark":"Trademark or Copyright","pages.reports.reasons.selfharm":"Self-Harm","pages.reports.reasons.harassment":"Harassment","pages.galleries.title":"Galleries","pages.galleries.empty_title":"No galleries have been published yet. Once they do they will appear here.","pages.galleries.search.loading_results":"Loading results...","pages.galleries.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.search.placeholder":"Search by Galleries","pages.galleries.search.placeholder_nfts":"Search by NFTs","pages.galleries.search.error":"Could not load search results. Please try again later.","pages.galleries.my_galleries.title":"My Galleries","pages.galleries.my_galleries.subtitle":"Manage your galleries","pages.galleries.my_galleries.new_gallery":"New Gallery","pages.galleries.my_galleries.no_galleries":"You have not created any galleries yet. To create a gallery, click on the \"Create Gallery\" button.","pages.galleries.my_galleries.succesfully_deleted":"Gallery successfully deleted","pages.galleries.my_galleries.successfully_created":"Gallery has been successfully created","pages.galleries.my_galleries.successfully_updated":"Gallery has been successfully updated","pages.galleries.my_galleries.new_gallery_no_nfts":"Creating a Gallery requires you to own an NFT.","pages.galleries.copy_gallery_link":"Copy Gallery Link","pages.galleries.my_nfts":"My NFTs","pages.galleries.value":"Value","pages.galleries.floor_price":"Floor Price","pages.galleries.nfts":"NFTs","pages.galleries.collections":"Collections","pages.galleries.galleries_count_simple":"{0} galleries|{1} gallery|[2,*] galleries","pages.galleries.galleries_count":"{0} {{count}} Galleries|{1} {{count}} Gallery|[2,*] {{count}} Galleries","pages.galleries.collections_count_simple":"{0} collections|{1} collection|[2,*] collections","pages.galleries.collections_count":"{0} {{count}} Collections|{1} {{count}} Collection|[2,*] {{count}} Collections","pages.galleries.nfts_count_simple":"{0} NFTs|{1} NFT|[2,*] NFTs","pages.galleries.nfts_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","pages.galleries.users_count_simple":"{0} users|{1} user|[2,*] users","pages.galleries.users_count":"{0} {{count}} Users|{1} {{count}} User|[2,*] {{count}} Users","pages.galleries.featuring":"Featuring","pages.galleries.curated_by":"Curated by","pages.galleries.worth_about":"Worth About","pages.galleries.valued_at":"valued at","pages.galleries.from":"From","pages.galleries.most_popular_galleries":"Most Popular Galleries","pages.galleries.newest_galleries":"Newest Galleries","pages.galleries.most_valuable_galleries":"Most Valuable Galleries","pages.galleries.most_popular":"Most Popular","pages.galleries.newest":"Newest","pages.galleries.most_valuable":"Most Valuable","pages.galleries.create.search_by_nfts":"Search by NFTs","pages.galleries.create.input_placeholder":"Enter gallery name","pages.galleries.create.title_too_long":"Gallery name must not exceed {{max}} characters.","pages.galleries.create.already_selected_nft":"NFT already exists in this gallery","pages.galleries.create.nft_missing_image":"Only NFTs with images can be added to galleries","pages.galleries.create.gallery_cover":"Gallery Cover","pages.galleries.create.gallery_cover_description":"The cover is used for the card on the gallery list page. While the cover is not a requirement it will allow you to add personality and stand out from the crowd.","pages.galleries.create.gallery_cover_information":"Image dimensions must be at least 287px x 190px, with a max size of 2 MB (JPG, PNG or GIF)","pages.galleries.create.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.create.templates.cover":"Cover","pages.galleries.create.templates.template":"Template","pages.galleries.create.templates.select":"Select Gallery Template","pages.galleries.create.templates.basic":"Basic Gallery","pages.galleries.create.templates.coming_soon":"More Coming Soon","pages.galleries.create.load_more_collections_one":"Load {{count}} More Collection","pages.galleries.create.load_more_collections_other":"Load {{count}} More Collections","pages.galleries.create.load_more_nfts":"Load More NFTs","pages.galleries.create.can_purchase":"You can purchase NFTs with these top NFT Marketplaces:","pages.galleries.create.must_own_one_nft":"You must own at least one (1) NFT in order to create a gallery.","pages.galleries.create.back_to_galleries":"Back to Galleries","pages.galleries.delete_modal.title":"Delete Gallery","pages.galleries.delete_modal.confirmation_text":"Are you sure you want to delete the gallery? Everything you've done will be deleted and you won't be able to get it back.","pages.galleries.consists_of_collections":"{0} This gallery consists of {{count}} collections|{1} This gallery consists of {{count}} collection|[2,*] This gallery consists of {{count}} collections","pages.galleries.guest_banner.title":"Craft the ultimate","pages.galleries.guest_banner.subtitle":"Pick your favorites, curate your gallery, & share it with the world.","pages.profile.title":"Profile","pages.token_panel.balance_tooltip":"Total percentage of the portfolio held in this token","pages.token_panel.insufficient_funds":"Insufficient Balance","pages.token_panel.error":"Dashbrd has failed to load token information. Please try again later.","pages.token_panel.failed_to_retrieve_transactions":"We were unable to fetch your transactions.","pages.token_panel.tabs.transaction_history":"Transaction History","pages.token_panel.tabs.history":"History","pages.token_panel.tabs.market_data":"Market Data","pages.token_panel.details.current_price":"Current Price","pages.token_panel.details.title":"Token Details","pages.token_panel.details.market_cap":"Market Cap","pages.token_panel.details.volume":"Daily Volume","pages.token_panel.details.supply":"Minted Supply","pages.token_panel.details.ath":"All-Time High","pages.token_panel.details.atl":"All-Time Low","pages.token_panel.chart.failed":"Dashbrd has failed to load chart information. Please try again later.","pages.transaction_details_panel.title":"Transaction Details","pages.transaction_details_panel.details.blockchain":"Blockchain","pages.transaction_details_panel.details.timestamp":"Timestamp","pages.transaction_details_panel.details.transaction_hash":"Transaction Hash","pages.transaction_details_panel.details.transaction_fee":"Transaction Fee","pages.transaction_details_panel.details.gas_price":"Gas Price","pages.transaction_details_panel.details.gas_used":"Gas Used","pages.transaction_details_panel.details.nonce":"Nonce","pages.send_receive_panel.send.labels.token_and_amount":"Token and Amount","pages.send_receive_panel.send.labels.destination_address":"Destination Address","pages.send_receive_panel.send.labels.projected_fee":"Projected Fee","pages.send_receive_panel.send.placeholders.enter_amount":"Enter Amount","pages.send_receive_panel.send.placeholders.insert_recipient_address":"Insert Recipient Address","pages.send_receive_panel.send.placeholders.projected_fee":"Projected Fee","pages.send_receive_panel.send.errors.amount":"Insufficient Funds: You do not have enough to cover the amount + fee.","pages.send_receive_panel.send.errors.destination":"Destination address is not correct. Check and input again.","pages.send_receive_panel.send.hints.token_price":"Token Price","pages.send_receive_panel.send.fees.Fast":"Fast","pages.send_receive_panel.send.fees.Avg":"Avg","pages.send_receive_panel.send.fees.Slow":"Slow","pages.send_receive_panel.send.search_dropdown.placeholder":"Search token","pages.send_receive_panel.send.search_dropdown.no_results":"No results","pages.send_receive_panel.send.search_dropdown.error":"Error occurred while searching tokens.","pages.send_receive_panel.send.transaction_time":"Transaction Time: ~{{ time }} minutes","pages.send_receive_panel.send.from":"From","pages.send_receive_panel.send.to":"To","pages.send_receive_panel.send.amount":"Amount","pages.send_receive_panel.send.fee":"Fee","pages.send_receive_panel.send.total_amount":"Total Amount","pages.send_receive_panel.send.waiting_message":"Review and verify the information on your MetaMask. Sign to send the transaction.","pages.send_receive_panel.send.waiting_spinner_text":"Waiting for confirmation...","pages.send_receive_panel.send.failed_message":"It looks like something went wrong while sending your transaction. Press 'Retry' to make another attempt.","pages.send_receive_panel.receive.alert":"Send only Polygon or Ethereum Network compatible tokens to this address or you could permanently lose your funds!","pages.settings.title":"Settings","pages.settings.sidebar.general":"General","pages.settings.sidebar.notifications":"Notifications","pages.settings.sidebar.session_history":"Sessions History","pages.settings.general.title":"Settings","pages.settings.general.subtitle":"Customize your App Experience","pages.settings.general.currency":"Currency","pages.settings.general.currency_subtitle":"Select your default currency which will be used throughout the app.","pages.settings.general.time_date":"Time & Date","pages.settings.general.time_date_subtitle":"Select how you want time and date be shown inside app.","pages.settings.general.date_format":"Date Format","pages.settings.general.time_format":"Time Format","pages.settings.general.timezone":"Timezone","pages.settings.general.set_defaults":"Set Defaults","pages.settings.general.set_defaults_content":"Reverting to the default settings will remove any customizations previously made. Are you sure?","pages.settings.general.save":"Save Settings","pages.settings.general.saved":"Your settings have been successfully saved","pages.wallet.title":"Wallet","pages.privacy_policy.title":"Privacy Policy","pages.terms_of_service.title":"Terms of Service","pagination.previous":"« Previous","pagination.next":"Next »","passwords.reset":"Your password has been reset!","passwords.sent":"We have emailed your password reset link!","passwords.throttled":"Please wait before retrying.","passwords.token":"This password reset token is invalid.","passwords.user":"We can't find a user with that email address.","urls.landing":"https://dashbrd.com","urls.cookie_policy":"https://dashbrd.com/cookie-policy","urls.privacy_policy":"https://dashbrd.com/privacy-policy","urls.terms_of_service":"https://dashbrd.com/terms-of-service","urls.twitter":"https://x.com/DashbrdApp","urls.discord":"https://discord.gg/MJyWKkCJ5k","urls.github":"https://github.com/ArdentHQ/dashbrd","urls.coingecko":"https://www.coingecko.com","urls.etherscan":"https://etherscan.io","urls.polygonscan":"https://polygonscan.com","urls.alchemy":"https://www.alchemy.com","urls.moralis":"https://moralis.io","urls.mnemonic":"https://www.mnemonichq.com","urls.opensea":"https://opensea.io/","urls.explorers.etherscan.token_transactions":"https://etherscan.io/token/{{token}}?a={{address}}","urls.explorers.etherscan.addresses":"https://etherscan.io/address/{{address}}","urls.explorers.etherscan.transactions":"https://etherscan.io/tx/{{id}}","urls.explorers.etherscan.nft":"https://etherscan.io/nft/{{address}}/{{nftId}}","urls.explorers.polygonscan.token_transactions":"https://polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.polygonscan.addresses":"https://polygonscan.com/address/{{address}}","urls.explorers.polygonscan.transactions":"https://polygonscan.com/tx/{{id}}","urls.explorers.polygonscan.nft":"https://polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.mumbai.token_transactions":"https://mumbai.polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.mumbai.addresses":"https://mumbai.polygonscan.com/address/{{address}}","urls.explorers.mumbai.transactions":"https://mumbai.polygonscan.com/tx/{{id}}","urls.explorers.mumbai.nft":"https://mumbai.polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.goerli.token_transactions":"https://goerli.etherscan.io/token/{{token}}?a={{address}}","urls.explorers.goerli.addresses":"https://goerli.etherscan.io/address/{{address}}","urls.explorers.goerli.transactions":"https://goerli.etherscan.io/tx/{{id}}","urls.explorers.goerli.nft":"https://goerli.etherscan.io/nft/{{address}}/{{nftId}}","urls.marketplaces.opensea.collection":"https://opensea.io/assets/{{network}}/{{address}}","urls.marketplaces.opensea.nft":"https://opensea.io/assets/{{network}}/{{address}}/{{nftId}}","urls.marketplaces.rarible.collection":"https://rarible.com/collection/{{address}}/items","urls.marketplaces.rarible.nft":"https://rarible.com/token/{{address}}:{{nftId}}","urls.marketplaces.blur.collection":"https://blur.io/collection/{{address}}","urls.marketplaces.blur.nft":"https://blur.io/asset/{{address}}/{{nftId}}","urls.marketplaces.looksrare.collection":"https://looksrare.org/collections/{{address}}","urls.marketplaces.looksrare.nft":"https://looksrare.org/collections/{{address}}/{{nftId}}","validation.accepted":"The {{attribute}} must be accepted.","validation.accepted_if":"The {{attribute}} must be accepted when {{other}} is {{value}}.","validation.active_url":"The {{attribute}} is not a valid URL.","validation.after":"The {{attribute}} must be a date after {{date}}.","validation.after_or_equal":"The {{attribute}} must be a date after or equal to {{date}}.","validation.alpha":"The {{attribute}} must only contain letters.","validation.alpha_dash":"The {{attribute}} must only contain letters, numbers, dashes and underscores.","validation.alpha_num":"The {{attribute}} must only contain letters and numbers.","validation.array":"The {{attribute}} must be an array.","validation.ascii":"The {{attribute}} must only contain single-byte alphanumeric characters and symbols.","validation.before":"The {{attribute}} must be a date before {{date}}.","validation.before_or_equal":"The {{attribute}} must be a date before or equal to {{date}}.","validation.between.array":"The {{attribute}} must have between {{min}} and {{max}} items.","validation.between.file":"The {{attribute}} must be between {{min}} and {{max}} kilobytes.","validation.between.numeric":"The {{attribute}} must be between {{min}} and {{max}}.","validation.between.string":"The {{attribute}} must be between {{min}} and {{max}} characters.","validation.boolean":"The {{attribute}} field must be true or false.","validation.confirmed":"The {{attribute}} confirmation does not match.","validation.current_password":"The password is incorrect.","validation.date":"The {{attribute}} is not a valid date.","validation.date_equals":"The {{attribute}} must be a date equal to {{date}}.","validation.date_format":"The {{attribute}} does not match the format {{format}}.","validation.decimal":"The {{attribute}} must have {{decimal}} decimal places.","validation.declined":"The {{attribute}} must be declined.","validation.declined_if":"The {{attribute}} must be declined when {{other}} is {{value}}.","validation.different":"The {{attribute}} and {{other}} must be different.","validation.digits":"The {{attribute}} must be {{digits}} digits.","validation.digits_between":"The {{attribute}} must be between {{min}} and {{max}} digits.","validation.dimensions":"The {{attribute}} has invalid image dimensions.","validation.distinct":"The {{attribute}} field has a duplicate value.","validation.doesnt_end_with":"The {{attribute}} may not end with one of the following: {{values}}.","validation.doesnt_start_with":"The {{attribute}} may not start with one of the following: {{values}}.","validation.email":"The {{attribute}} must be a valid email address.","validation.ends_with":"The {{attribute}} must end with one of the following: {{values}}.","validation.enum":"The selected {{attribute}} is invalid.","validation.exists":"The selected {{attribute}} is invalid.","validation.file":"The {{attribute}} must be a file.","validation.filled":"The {{attribute}} field must have a value.","validation.gt.array":"The {{attribute}} must have more than {{value}} items.","validation.gt.file":"The {{attribute}} must be greater than {{value}} kilobytes.","validation.gt.numeric":"The {{attribute}} must be greater than {{value}}.","validation.gt.string":"The {{attribute}} must be greater than {{value}} characters.","validation.gte.array":"The {{attribute}} must have {{value}} items or more.","validation.gte.file":"The {{attribute}} must be greater than or equal to {{value}} kilobytes.","validation.gte.numeric":"The {{attribute}} must be greater than or equal to {{value}}.","validation.gte.string":"The {{attribute}} must be greater than or equal to {{value}} characters.","validation.image":"The {{attribute}} must be an image.","validation.in":"The selected {{attribute}} is invalid.","validation.in_array":"The {{attribute}} field does not exist in {{other}}.","validation.integer":"The {{attribute}} must be an integer.","validation.ip":"The {{attribute}} must be a valid IP address.","validation.ipv4":"The {{attribute}} must be a valid IPv4 address.","validation.ipv6":"The {{attribute}} must be a valid IPv6 address.","validation.json":"The {{attribute}} must be a valid JSON string.","validation.lowercase":"The {{attribute}} must be lowercase.","validation.lt.array":"The {{attribute}} must have less than {{value}} items.","validation.lt.file":"The {{attribute}} must be less than {{value}} kilobytes.","validation.lt.numeric":"The {{attribute}} must be less than {{value}}.","validation.lt.string":"The {{attribute}} must be less than {{value}} characters.","validation.lte.array":"The {{attribute}} must not have more than {{value}} items.","validation.lte.file":"The {{attribute}} must be less than or equal to {{value}} kilobytes.","validation.lte.numeric":"The {{attribute}} must be less than or equal to {{value}}.","validation.lte.string":"The {{attribute}} must be less than or equal to {{value}} characters.","validation.mac_address":"The {{attribute}} must be a valid MAC address.","validation.max.array":"The {{attribute}} must not have more than {{max}} items.","validation.max.file":"The {{attribute}} must not be greater than {{max}} kilobytes.","validation.max.numeric":"The {{attribute}} must not be greater than {{max}}.","validation.max.string":"The {{attribute}} must not be greater than {{max}} characters.","validation.max_digits":"The {{attribute}} must not have more than {{max}} digits.","validation.mimes":"The {{attribute}} must be a file of type: {{values}}.","validation.mimetypes":"The {{attribute}} must be a file of type: {{values}}.","validation.min.array":"The {{attribute}} must have at least {{min}} items.","validation.min.file":"The {{attribute}} must be at least {{min}} kilobytes.","validation.min.numeric":"The {{attribute}} must be at least {{min}}.","validation.min.string":"The {{attribute}} must be at least {{min}} characters.","validation.min_digits":"The {{attribute}} must have at least {{min}} digits.","validation.missing":"The {{attribute}} field must be missing.","validation.missing_if":"The {{attribute}} field must be missing when {{other}} is {{value}}.","validation.missing_unless":"The {{attribute}} field must be missing unless {{other}} is {{value}}.","validation.missing_with":"The {{attribute}} field must be missing when {{values}} is present.","validation.missing_with_all":"The {{attribute}} field must be missing when {{values}} are present.","validation.multiple_of":"The {{attribute}} must be a multiple of {{value}}.","validation.not_in":"The selected {{attribute}} is invalid.","validation.not_regex":"The {{attribute}} format is invalid.","validation.numeric":"The {{attribute}} must be a number.","validation.password.letters":"The {{attribute}} must contain at least one letter.","validation.password.mixed":"The {{attribute}} must contain at least one uppercase and one lowercase letter.","validation.password.numbers":"The {{attribute}} must contain at least one number.","validation.password.symbols":"The {{attribute}} must contain at least one symbol.","validation.password.uncompromised":"The given {{attribute}} has appeared in a data leak. Please choose a different {{attribute}}.","validation.present":"The {{attribute}} field must be present.","validation.prohibited":"The {{attribute}} field is prohibited.","validation.prohibited_if":"The {{attribute}} field is prohibited when {{other}} is {{value}}.","validation.prohibited_unless":"The {{attribute}} field is prohibited unless {{other}} is in {{values}}.","validation.prohibits":"The {{attribute}} field prohibits {{other}} from being present.","validation.regex":"The {{attribute}} format is invalid.","validation.required":"The {{attribute}} field is required.","validation.required_array_keys":"The {{attribute}} field must contain entries for: {{values}}.","validation.required_if":"The {{attribute}} field is required when {{other}} is {{value}}.","validation.required_if_accepted":"The {{attribute}} field is required when {{other}} is accepted.","validation.required_unless":"The {{attribute}} field is required unless {{other}} is in {{values}}.","validation.required_with":"The {{attribute}} field is required when {{values}} is present.","validation.required_with_all":"The {{attribute}} field is required when {{values}} are present.","validation.required_without":"The {{attribute}} field is required when {{values}} is not present.","validation.required_without_all":"The {{attribute}} field is required when none of {{values}} are present.","validation.same":"The {{attribute}} and {{other}} must match.","validation.size.array":"The {{attribute}} must contain {{size}} items.","validation.size.file":"The {{attribute}} must be {{size}} kilobytes.","validation.size.numeric":"The {{attribute}} must be {{size}}.","validation.size.string":"The {{attribute}} must be {{size}} characters.","validation.starts_with":"The {{attribute}} must start with one of the following: {{values}}.","validation.string":"The {{attribute}} must be a string.","validation.timezone":"The {{attribute}} must be a valid timezone.","validation.unique":"The {{attribute}} has already been taken.","validation.uploaded":"The {{attribute}} failed to upload.","validation.uppercase":"The {{attribute}} must be uppercase.","validation.url":"The {{attribute}} must be a valid URL.","validation.ulid":"The {{attribute}} must be a valid ULID.","validation.uuid":"The {{attribute}} must be a valid UUID.","validation.custom.attribute-name.rule-name":"custom-message","validation.unsupported_currency_code":"The currency code you provided is invalid or not supported.","validation.unsupported_period":"The period you provided is invalid or not supported.","validation.unsupported_token_symbol":"The token symbol you provided is invalid or not supported.","validation.gallery_title_required":"Gallery name is required.","validation.gallery_title_max_characters":"The gallery name should not exceed 50 characters.","validation.gallery_title_invalid":"The gallery name is invalid.","validation.nfts_required":"Please add at least one NFT.","validation.nfts_max_size":"Galleries can contain no more than {{limit}} NFTs","validation.invalid_nfts":"The NFT in position {{position}} is invalid, please select another one.","validation.invalid_cover":"You have selected an invalid cover image, please try another one."} \ No newline at end of file +{"auth.welcome":"Welcome to Dashbrd","auth.logged_in":"You're logged in!","auth.log_out":"Log out","auth.failed":"These credentials do not match our records.","auth.session_timeout":"Your session has timed out. Please refresh the page and try connecting your account again.","auth.session_timeout_modal":"Seems like your session has timed out. Please connnect your wallet again.","auth.password":"The provided password is incorrect.","auth.throttle":"Too many login attempts. Please try again in {{seconds}} seconds.","auth.wallet.connecting":"Connecting …","auth.wallet.waiting_for_signature":"Waiting for Signature …","auth.wallet.switching_wallet":"Switching Wallet …","auth.wallet.connect":"Connect Wallet","auth.wallet.connect_long":"Connect Your Wallet to Get Started","auth.wallet.sign_subtitle":"Connect Your Wallet to Continue","auth.wallet.disconnect":"Disconnect Wallet","auth.wallet.install":"Install MetaMask","auth.wallet.install_long":"Install MetaMask to Get Started","auth.wallet.sign":"Sign Message","auth.wallet.sign_message":"Welcome to Dashbrd. In order to login, sign this message with your wallet. It doesn't cost you anything!\n\nSigning ID (you can ignore this): {{nonce}}","auth.wallet.connect_subtitle":"Click on the MetaMask icon in your browser to confirm the action and connect your wallet.","auth.wallet.requires_signature":"In order to prevent impersonation, we require a signature to perform this action. This signature is only a signed message and does not give any access to your wallet.","auth.errors.metamask.no_account":"No account found. Please connect your wallet and try again.","auth.errors.metamask.generic":"Connection attempt error. Please retry and follow the steps to connect your wallet.","auth.errors.metamask.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","auth.errors.metamask.provider_missing":"You don't have MetaMask installed in your browser. Please install and try again.","auth.errors.metamask.user_rejected":"It looks like you cancelled signing of the authentication message. Please try again.","auth.errors.metamask.provider_not_set":"Ethereum provider is not set","auth.validation.wallet_login_failed":"There was a problem trying to verify your signature. Please try again.","auth.validation.invalid_address":"Your wallet address is invalid. Please try again.","auth.validation.invalid_signature":"Signature is invalid. Please try again.","auth.validation.invalid_network":"Please switch to Polygon or Ethereum Mainnet in your MetaMask plugin to connect to Dashbrd.","common.add":"Add","common.amount":"Amount","common.balance":"Balance","common.cancel":"Cancel","common.delete":"Delete","common.edit":"Edit","common.confirm":"Confirm","common.connect":"Connect","common.continue":"Continue","common.done":"Done","common.filter":"Filter","common.items":"Items","common.receive":"Receive","common.received":"Received","common.retry":"Retry","common.records":"Records","common.save":"Save","common.send":"Send","common.sent":"Sent","common.show":"Show","common.searching":"Searching...","common.other":"Other","common.owned":"Owned","common.token":"Token","common.tokens":"Tokens","common.wallet":"Wallet","common.pending":"Pending","common.publish":"Publish","common.empty":"Empty","common.your_address":"Your Address","common.warning":"Warning","common.my_address":"My Address","common.my_wallet":"My Wallet","common.my_balance":"My Balance","common.na":"N/A","common.simple_plural_without_data":"One comment|Many comments","common.simple_plural_with_data":"{{count}} comment|{{count}} comments","common.advanced_plural_without_data":"{0} no comments yet|{1} 1 comment|[2,*] Many comments","common.advanced_plural_with_data":"{0} no comments yet|{1} 1 comment|[2,*] {{count}} comments","common.copy_clipboard":"Copy to Clipboard","common.copy":"Copy","common.download":"Download","common.zoom":"Zoom","common.my_collection":"My Collection","common.max":"Max","common.chain":"Chain","common.copied":"Copied!","common.coming_soon":"Coming Soon","common.more_details":"More Details","common.close":"Close","common.close_toast":"Close toast","common.loading":"Loading","common.price":"Price","common.market_cap":"Market Cap","common.volume":"Volume {{frequency}}","common.value":"Value","common.last_n_days":"Last {{count}} Days","common.details":"Details","common.view_all":"View All","common.view_more_on_polygonscan":"View More on Polygonscan","common.view_more_on_etherscan":"View More on Etherscan","common.view_nft_on_etherscan":"View NFT on Etherscan","common.view_nft_on_polygonscan":"View NFT on Polygonscan","common.view_nft_on_goerli_tesnet":"View NFT on Goerli Testnet Explorer","common.view_nft_on_mumbai_tesnet":"View NFT on Mumbai Testnet Explorer","common.polygonscan":"Polygonscan","common.etherscan":"Etherscan","common.featured":"Featured","common.floor_price":"Floor Price","common.floor":"Floor","common.supply":"Supply","common.owners":"Owners","common.created":"Created","common.nft":"NFT","common.collection":"Collection","common.collections":"Collections","common.gallery":"Gallery","common.basic_gallery":"Basic Gallery","common.gallery_name":"Gallery Name","common.create_gallery":"Create Gallery","common.external_link":"External Link","common.follow_link":"Follow Link","common.cover":"Cover","common.template":"Template","common.page":"Page","common.polygon":"Polygon","common.ethereum":"Ethereum","common.mumbai":"Mumbai","common.goerli":"Goerli","common.of":"of","common.pagination_input_placeholder":"Enter the page number","common.pagination_input_placeholder_mobile":"Page number","common.nft_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","common.nft_gallery":"NFT Gallery","common.unable_to_retrieve_image":"Unable to retrieve your NFT image at this time","common.optional":"Optional","common.selected":"Selected","common.select":"Select","common.preview":"Preview","common.image_size_error":"Image size must not exceed 2MB","common.image_dimensions_error":"Image dimensions must exceed 287px x 190px","common.write_to_confirm":"Write {{word}} to confirm","common.n_hours":"{0} {{count}} hour|{1} {{count}} hour|[2,*] {{count}} hours","common.n_minutes":"{0} {{count}} minute|{1} {{count}} minute|[2,*] {{count}} minutes","common.report":"Report","common.n_nfts":"{0}NFTs|{1}NFT|[2,*]NFTs","common.n_collections":"{0}collections|{1}collection|[2,*]collections","common.back":"Back","common.back_to":"Back to","common.name":"Name","common.type":"Type","common.from":"From","common.to":"To","common.sale_price":"Sale Price","common.recent_activity":"Recent Activity","common.time":"Time","common.datetime.few_seconds_ago":"A few seconds ago","common.datetime.minutes_ago":"{0} Less than a minute ago|{1} A minute ago|[2,*] {{count}} minutes ago","common.datetime.hours_ago":"{0} Less than an hour ago|{1} An hour ago|[2,*] {{count}} hours ago","common.datetime.days_ago":"{0} Less than a day ago|{1} A day ago|[2,*] {{count}} days ago","common.datetime.weeks_ago":"{0} Less than a week ago|{1} A week ago|[2,*] {{count}} weeks ago","common.datetime.months_ago":"{0} Less than a month ago|{1} A month ago|[2,*] {{count}} months ago","common.datetime.years_ago":"{0} Less than a year ago|{1} A year ago|[2,*] {{count}} years ago","common.no_traits_found":"No traits found.","common.empty_transactions":"You don't have any transactions yet. Once transactions have been made, they will show up here.","common.error":"Error","common.refresh_metadata":"Refresh Metadata","common.refreshing_metadata":"Refreshing Metadata... Please check back later.","common.pending_confirmation":"Pending Confirmation","common.confirmed_transaction":"Confirmed Transaction","common.transaction_error":"Transaction Error","common.transaction_error_description_first_part":"Your transaction encountered an error. View this transaction on","common.transaction_error_description_second_part":"for more details.","common.home":"Home","common.contact":"Contact","common.menu":"Menu","common.website":"Website","common.twitter":"Twitter","common.discord":"Discord","common.sort":"Sort","common.published":"Published","common.drafts":"Drafts","footer.copyright":"{{year}} © Dashbrd. All rights reserved.","footer.all_rights_reserved":"All rights reserved","footer.privacy_policy":"Privacy Policy","footer.terms_of_service":"Terms of Service","footer.powered_by":"Powered by","format.fiat":"{{ value, currency }}","format.number":"{{ value, number }}","metatags.home.title":"Dashbrd | Web3 Portfolio Management Made Simple","metatags.home.description":"Simplify your Web3 journey with Dashbrd. Manage your portfolio of tokens, NFTs, and other digital collectibles across the Ethereum and Polygon blockchains.","metatags.home.image":"/images/meta/home.png","metatags.error.title":"Error {{code}} | Dashbrd","metatags.wallet.title":"My Wallet | Dashbrd","metatags.galleries.title":"Top NFT Galleries | Dashbrd","metatags.galleries.image":"/images/meta/nft-galleries.png","metatags.galleries.description":"Explore user published NFT galleries to find custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.most_popular.title":"Most Popular Galleries | Dashbrd","metatags.galleries.most_popular.image":"/images/meta/most-popular-nft-galleries.png","metatags.galleries.most_popular.description":"Explore and discover Most Popular NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.newest.title":"Newest Galleries | Dashbrd","metatags.galleries.newest.image":"/images/meta/newest-nft-galleries.png","metatags.galleries.newest.description":"Explore and discover most recent NFT galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More","metatags.galleries.most_valuable.title":"Most Valuable Galleries | Dashbrd","metatags.galleries.most_valuable.image":"/images/meta/most-valuable-nft-galleries.png","metatags.galleries.most_valuable.description":"Explore and discover Most Valuable NFT Galleries created by our users featuring custom curated PFPs, Digital Collectibles, & More.","metatags.galleries.view.title":"{{name}} | Dashbrd","metatags.galleries.view.description":"{{name}} | A Curated NFT Gallery at Dashbrd","metatags.galleries.view.image":"/images/meta/nft-gallery.png","metatags.my_galleries.title":"My Galleries | Dashbrd","metatags.my_galleries.title_draft":"My Draft Galleries | Dashbrd","metatags.my_galleries.create.title":"Create Gallery | Dashbrd","metatags.my_galleries.edit.title":"Edit {{name}} | Dashbrd","metatags.collections.title":"Collections | Dashbrd","metatags.collections.view.title":"{{name}} Collection | Dashbrd","metatags.collections.view.description":"Immerse yourself in the intricate details of {{name}} collection, featuring remarkable digital assets. Start your NFT journey today!","metatags.collections.view.image":"/images/meta/nft-collection.png","metatags.nfts.view.title":"{{nft}} NFT | Dashbrd","metatags.nfts.view.description":"Uncover the complete story of {{nft}} NFT from the {{collection}} collection, delving into its unique attributes and distinctive features.","metatags.nfts.view.image":"/images/meta/nft-details.png","metatags.settings.title":"Settings | Dashbrd","metatags.login.title":"Login | Dashbrd","metatags.privacy_policy.title":"Privacy Policy | Dashbrd","metatags.privacy_policy.description":"Dashbrd’s privacy policy outlines the information we collect and explains your choices surrounding how we use information about you.","metatags.terms_of_service.title":"Terms of Service | Dashbrd","metatags.terms_of_service.description":"These Terms of Service cover your use and access to services, products or websites of Dashbrd.","metatags.cookie_policy.title":"Cookie Policy | Dashbrd","metatags.cookie_policy.description":"Dashbrd uses cookies to make the website more user-friendly. Find out about the main types of cookies we use, and what we use them for.","metatags.welcome.title":"Welcome to Dashbrd | Web3 Simplified","pages.onboarding.title":"Get Started","pages.onboarding.heading":"Your monkeys were bored and ran off, we are trying to round them up.","pages.onboarding.message":"We are setting up your account. This process usually takes just a few minutes, but can take up to 15 minutes.","pages.error.heading":"Oops, something went wrong ...","pages.error.message":"Please try again or get in touch if the issue persists.","pages.maintenance.title":"Dashbrd is currently down for scheduled maintenance.","pages.maintenance.description":"We expect to be back soon. Thanks for your patience.","pages.dashboard.title":"My Wallet","pages.dashboard.breakdown.title":"Portfolio Breakdown","pages.dashboard.line_chart.data_error":"Could not load chart data","pages.collections.title":"Collections","pages.collections.collections":"Collections","pages.collections.collection_value":"Collection Value","pages.collections.nfts_owned":"NFTs Owned","pages.collections.header_title":"You own <0>{{nftsCount}} {{nfts}} across <0>{{collectionsCount}} {{collections}}, worth about <0><1>{{worth}}","pages.collections.search_placeholder":"Search by Collection","pages.collections.properties":"Properties","pages.collections.collections_network":"Collections Network","pages.collections.property_search_placeholder":"Feature Search","pages.collections.floor_price":"Floor Price","pages.collections.value":"Value","pages.collections.rarity":"Rarity","pages.collections.report":"Report","pages.collections.hide_collection":"Hide Collection","pages.collections.unhide_collection":"Unhide Collection","pages.collections.no_collections":"You do not own any NFTs yet. Once you do they will be shown here.","pages.collections.all_collections_hidden":"You have hidden all your collections. Unhide and they will appear here.","pages.collections.about_collection":"About Collection","pages.collections.show_hidden":"Show Hidden","pages.collections.show_my_collection":"Show My Collection","pages.collections.owned":"Owned","pages.collections.activities.loading_activities":"We're fetching Activity for this NFT, please hang tight, this can take a while.","pages.collections.activities.ignores_activities":"We don't support activity history for this collection yet.","pages.collections.activities.no_activity":"This collection does not have any activity yet.","pages.collections.activities.types.LABEL_MINT":"Mint","pages.collections.activities.types.LABEL_TRANSFER":"Transfer","pages.collections.activities.types.LABEL_SALE":"Sale","pages.collections.search.loading_results":"Loading results...","pages.collections.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.collections.search.no_results_with_filters":"We could not find anything matching your filters, please try again!","pages.collections.search.no_results_ownership":"You do not own any NFTs in this collection","pages.collections.search.error":"Could not load search results. Please try again later.","pages.collections.sorting.token_number":"Token Number","pages.collections.sorting.recently_received":"Recently Received","pages.collections.sorting.recently_created":"Recently Created","pages.collections.sorting.oldest_collection":"Oldest Collection","pages.collections.traits.description":"List of NFT traits by % of occurrence in the collection","pages.collections.traits.no_traits":"No Properties can be found for this NFT","pages.collections.menu.collection":"Collection","pages.collections.menu.activity":"Activity","pages.collections.hidden_modal.collection_hidden":"Collection Hidden","pages.collections.hidden_modal.description":"This collection is currently set to Hidden. Are you sure you want to unhide this collection? You can\n reset the collection to hidden from the collection menu.","pages.collections.hidden_modal.unhide":"Unhide","pages.collections.hidden_modal.error":"Something went wrong. Please try again.","pages.collections.external_modal.you_wish_continue":"You are about to leave Dashbrd to an external website. Dashbrd has no control over the content of\n this site. Are you sure you wish to continue?","pages.collections.external_modal.not_show":"Do not show this message again.","pages.collections.refresh.title":"Refresh your collection","pages.collections.refresh.notice":"You can refresh data every 15 minutes.","pages.collections.refresh.notice_wait":"Please wait. You can refresh data every 15 minutes.","pages.collections.refresh.toast":"We're updating information for your collection.","pages.nfts.nft":"nft","pages.nfts.about_nft":"About NFT","pages.nfts.owned_by":"Owned by","pages.nfts.collection_image":"collection image","pages.nfts.menu.properties":"Properties","pages.nfts.menu.activity":"Activity","pages.reports.title":"Submit a Report","pages.reports.description":"Thanks for looking out by reporting things that break the rules. Let us know what's happening and we'll receive the report.","pages.reports.success":"Thank you for your report. We'll review it and see if it breaks our ToS.","pages.reports.failed":"Something went wrong. Please try again.","pages.reports.throttle":"You have made too many requests. Please wait {{time}} before reporting again.","pages.reports.reported":"You have already reported this {{model}}.","pages.reports.reasons.spam":"Spam","pages.reports.reasons.violence":"Promoting Violence","pages.reports.reasons.hate":"Hate","pages.reports.reasons.inappropriate_content":"Inappropriate Content","pages.reports.reasons.impersonation":"Impersonation","pages.reports.reasons.trademark":"Trademark or Copyright","pages.reports.reasons.selfharm":"Self-Harm","pages.reports.reasons.harassment":"Harassment","pages.galleries.title":"Galleries","pages.galleries.empty_title":"No galleries have been published yet. Once they do they will appear here.","pages.galleries.search.loading_results":"Loading results...","pages.galleries.search.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.search.placeholder":"Search by Galleries","pages.galleries.search.placeholder_nfts":"Search by NFTs","pages.galleries.search.error":"Could not load search results. Please try again later.","pages.galleries.my_galleries.title":"My Galleries","pages.galleries.my_galleries.subtitle":"Manage your galleries","pages.galleries.my_galleries.new_gallery":"New Gallery","pages.galleries.my_galleries.no_galleries":"You have not created any galleries yet. To create a gallery, click on the \"Create Gallery\" button.","pages.galleries.my_galleries.succesfully_deleted":"Gallery successfully deleted","pages.galleries.my_galleries.successfully_created":"Gallery has been successfully created","pages.galleries.my_galleries.successfully_updated":"Gallery has been successfully updated","pages.galleries.my_galleries.new_gallery_no_nfts":"Creating a Gallery requires you to own an NFT.","pages.galleries.copy_gallery_link":"Copy Gallery Link","pages.galleries.my_nfts":"My NFTs","pages.galleries.value":"Value","pages.galleries.floor_price":"Floor Price","pages.galleries.nfts":"NFTs","pages.galleries.collections":"Collections","pages.galleries.galleries_count_simple":"{0} galleries|{1} gallery|[2,*] galleries","pages.galleries.galleries_count":"{0} {{count}} Galleries|{1} {{count}} Gallery|[2,*] {{count}} Galleries","pages.galleries.collections_count_simple":"{0} collections|{1} collection|[2,*] collections","pages.galleries.collections_count":"{0} {{count}} Collections|{1} {{count}} Collection|[2,*] {{count}} Collections","pages.galleries.nfts_count_simple":"{0} NFTs|{1} NFT|[2,*] NFTs","pages.galleries.nfts_count":"{0} {{count}} NFTs|{1} {{count}} NFT|[2,*] {{count}} NFTs","pages.galleries.users_count_simple":"{0} users|{1} user|[2,*] users","pages.galleries.users_count":"{0} {{count}} Users|{1} {{count}} User|[2,*] {{count}} Users","pages.galleries.featuring":"Featuring","pages.galleries.curated_by":"Curated by","pages.galleries.worth_about":"Worth About","pages.galleries.valued_at":"valued at","pages.galleries.from":"From","pages.galleries.most_popular_galleries":"Most Popular Galleries","pages.galleries.newest_galleries":"Newest Galleries","pages.galleries.most_valuable_galleries":"Most Valuable Galleries","pages.galleries.most_popular":"Most Popular","pages.galleries.newest":"Newest","pages.galleries.most_valuable":"Most Valuable","pages.galleries.create.search_by_nfts":"Search by NFTs","pages.galleries.create.input_placeholder":"Enter gallery name","pages.galleries.create.title_too_long":"Gallery name must not exceed {{max}} characters.","pages.galleries.create.already_selected_nft":"NFT already exists in this gallery","pages.galleries.create.nft_missing_image":"Only NFTs with images can be added to galleries","pages.galleries.create.gallery_cover":"Gallery Cover","pages.galleries.create.gallery_cover_description":"The cover is used for the card on the gallery list page. While the cover is not a requirement it will allow you to add personality and stand out from the crowd.","pages.galleries.create.gallery_cover_information":"Image dimensions must be at least 287px x 190px, with a max size of 2 MB (JPG, PNG or GIF)","pages.galleries.create.no_results":"We could not find anything matching your search criteria, please try again!","pages.galleries.create.templates.cover":"Cover","pages.galleries.create.templates.template":"Template","pages.galleries.create.templates.select":"Select Gallery Template","pages.galleries.create.templates.basic":"Basic Gallery","pages.galleries.create.templates.coming_soon":"More Coming Soon","pages.galleries.create.load_more_collections_one":"Load {{count}} More Collection","pages.galleries.create.load_more_collections_other":"Load {{count}} More Collections","pages.galleries.create.load_more_nfts":"Load More NFTs","pages.galleries.create.can_purchase":"You can purchase NFTs with these top NFT Marketplaces:","pages.galleries.create.must_own_one_nft":"You must own at least one (1) NFT in order to create a gallery.","pages.galleries.create.back_to_galleries":"Back to Galleries","pages.galleries.create.draft_saved":"Draft Saved","pages.galleries.create.saving_to_draft":"Saving to draft","pages.galleries.delete_modal.title":"Delete Gallery","pages.galleries.delete_modal.confirmation_text":"Are you sure you want to delete the gallery? Everything you've done will be deleted and you won't be able to get it back.","pages.galleries.consists_of_collections":"{0} This gallery consists of {{count}} collections|{1} This gallery consists of {{count}} collection|[2,*] This gallery consists of {{count}} collections","pages.galleries.guest_banner.title":"Craft the ultimate","pages.galleries.guest_banner.subtitle":"Pick your favorites, curate your gallery, & share it with the world.","pages.profile.title":"Profile","pages.token_panel.balance_tooltip":"Total percentage of the portfolio held in this token","pages.token_panel.insufficient_funds":"Insufficient Balance","pages.token_panel.error":"Dashbrd has failed to load token information. Please try again later.","pages.token_panel.failed_to_retrieve_transactions":"We were unable to fetch your transactions.","pages.token_panel.tabs.transaction_history":"Transaction History","pages.token_panel.tabs.history":"History","pages.token_panel.tabs.market_data":"Market Data","pages.token_panel.details.current_price":"Current Price","pages.token_panel.details.title":"Token Details","pages.token_panel.details.market_cap":"Market Cap","pages.token_panel.details.volume":"Daily Volume","pages.token_panel.details.supply":"Minted Supply","pages.token_panel.details.ath":"All-Time High","pages.token_panel.details.atl":"All-Time Low","pages.token_panel.chart.failed":"Dashbrd has failed to load chart information. Please try again later.","pages.transaction_details_panel.title":"Transaction Details","pages.transaction_details_panel.details.blockchain":"Blockchain","pages.transaction_details_panel.details.timestamp":"Timestamp","pages.transaction_details_panel.details.transaction_hash":"Transaction Hash","pages.transaction_details_panel.details.transaction_fee":"Transaction Fee","pages.transaction_details_panel.details.gas_price":"Gas Price","pages.transaction_details_panel.details.gas_used":"Gas Used","pages.transaction_details_panel.details.nonce":"Nonce","pages.send_receive_panel.send.labels.token_and_amount":"Token and Amount","pages.send_receive_panel.send.labels.destination_address":"Destination Address","pages.send_receive_panel.send.labels.projected_fee":"Projected Fee","pages.send_receive_panel.send.placeholders.enter_amount":"Enter Amount","pages.send_receive_panel.send.placeholders.insert_recipient_address":"Insert Recipient Address","pages.send_receive_panel.send.placeholders.projected_fee":"Projected Fee","pages.send_receive_panel.send.errors.amount":"Insufficient Funds: You do not have enough to cover the amount + fee.","pages.send_receive_panel.send.errors.destination":"Destination address is not correct. Check and input again.","pages.send_receive_panel.send.hints.token_price":"Token Price","pages.send_receive_panel.send.fees.Fast":"Fast","pages.send_receive_panel.send.fees.Avg":"Avg","pages.send_receive_panel.send.fees.Slow":"Slow","pages.send_receive_panel.send.search_dropdown.placeholder":"Search token","pages.send_receive_panel.send.search_dropdown.no_results":"No results","pages.send_receive_panel.send.search_dropdown.error":"Error occurred while searching tokens.","pages.send_receive_panel.send.transaction_time":"Transaction Time: ~{{ time }} minutes","pages.send_receive_panel.send.from":"From","pages.send_receive_panel.send.to":"To","pages.send_receive_panel.send.amount":"Amount","pages.send_receive_panel.send.fee":"Fee","pages.send_receive_panel.send.total_amount":"Total Amount","pages.send_receive_panel.send.waiting_message":"Review and verify the information on your MetaMask. Sign to send the transaction.","pages.send_receive_panel.send.waiting_spinner_text":"Waiting for confirmation...","pages.send_receive_panel.send.failed_message":"It looks like something went wrong while sending your transaction. Press 'Retry' to make another attempt.","pages.send_receive_panel.receive.alert":"Send only Polygon or Ethereum Network compatible tokens to this address or you could permanently lose your funds!","pages.settings.title":"Settings","pages.settings.sidebar.general":"General","pages.settings.sidebar.notifications":"Notifications","pages.settings.sidebar.session_history":"Sessions History","pages.settings.general.title":"Settings","pages.settings.general.subtitle":"Customize your App Experience","pages.settings.general.currency":"Currency","pages.settings.general.currency_subtitle":"Select your default currency which will be used throughout the app.","pages.settings.general.time_date":"Time & Date","pages.settings.general.time_date_subtitle":"Select how you want time and date be shown inside app.","pages.settings.general.date_format":"Date Format","pages.settings.general.time_format":"Time Format","pages.settings.general.timezone":"Timezone","pages.settings.general.set_defaults":"Set Defaults","pages.settings.general.set_defaults_content":"Reverting to the default settings will remove any customizations previously made. Are you sure?","pages.settings.general.save":"Save Settings","pages.settings.general.saved":"Your settings have been successfully saved","pages.wallet.title":"Wallet","pages.privacy_policy.title":"Privacy Policy","pages.terms_of_service.title":"Terms of Service","pagination.previous":"« Previous","pagination.next":"Next »","passwords.reset":"Your password has been reset!","passwords.sent":"We have emailed your password reset link!","passwords.throttled":"Please wait before retrying.","passwords.token":"This password reset token is invalid.","passwords.user":"We can't find a user with that email address.","urls.landing":"https://dashbrd.com","urls.cookie_policy":"https://dashbrd.com/cookie-policy","urls.privacy_policy":"https://dashbrd.com/privacy-policy","urls.terms_of_service":"https://dashbrd.com/terms-of-service","urls.twitter":"https://x.com/DashbrdApp","urls.discord":"https://discord.gg/MJyWKkCJ5k","urls.github":"https://github.com/ArdentHQ/dashbrd","urls.coingecko":"https://www.coingecko.com","urls.etherscan":"https://etherscan.io","urls.polygonscan":"https://polygonscan.com","urls.alchemy":"https://www.alchemy.com","urls.moralis":"https://moralis.io","urls.mnemonic":"https://www.mnemonichq.com","urls.opensea":"https://opensea.io/","urls.explorers.etherscan.token_transactions":"https://etherscan.io/token/{{token}}?a={{address}}","urls.explorers.etherscan.addresses":"https://etherscan.io/address/{{address}}","urls.explorers.etherscan.transactions":"https://etherscan.io/tx/{{id}}","urls.explorers.etherscan.nft":"https://etherscan.io/nft/{{address}}/{{nftId}}","urls.explorers.polygonscan.token_transactions":"https://polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.polygonscan.addresses":"https://polygonscan.com/address/{{address}}","urls.explorers.polygonscan.transactions":"https://polygonscan.com/tx/{{id}}","urls.explorers.polygonscan.nft":"https://polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.mumbai.token_transactions":"https://mumbai.polygonscan.com/token/{{token}}?a={{address}}","urls.explorers.mumbai.addresses":"https://mumbai.polygonscan.com/address/{{address}}","urls.explorers.mumbai.transactions":"https://mumbai.polygonscan.com/tx/{{id}}","urls.explorers.mumbai.nft":"https://mumbai.polygonscan.com/nft/{{address}}/{{nftId}}","urls.explorers.goerli.token_transactions":"https://goerli.etherscan.io/token/{{token}}?a={{address}}","urls.explorers.goerli.addresses":"https://goerli.etherscan.io/address/{{address}}","urls.explorers.goerli.transactions":"https://goerli.etherscan.io/tx/{{id}}","urls.explorers.goerli.nft":"https://goerli.etherscan.io/nft/{{address}}/{{nftId}}","urls.marketplaces.opensea.collection":"https://opensea.io/assets/{{network}}/{{address}}","urls.marketplaces.opensea.nft":"https://opensea.io/assets/{{network}}/{{address}}/{{nftId}}","urls.marketplaces.rarible.collection":"https://rarible.com/collection/{{address}}/items","urls.marketplaces.rarible.nft":"https://rarible.com/token/{{address}}:{{nftId}}","urls.marketplaces.blur.collection":"https://blur.io/collection/{{address}}","urls.marketplaces.blur.nft":"https://blur.io/asset/{{address}}/{{nftId}}","urls.marketplaces.looksrare.collection":"https://looksrare.org/collections/{{address}}","urls.marketplaces.looksrare.nft":"https://looksrare.org/collections/{{address}}/{{nftId}}","validation.accepted":"The {{attribute}} must be accepted.","validation.accepted_if":"The {{attribute}} must be accepted when {{other}} is {{value}}.","validation.active_url":"The {{attribute}} is not a valid URL.","validation.after":"The {{attribute}} must be a date after {{date}}.","validation.after_or_equal":"The {{attribute}} must be a date after or equal to {{date}}.","validation.alpha":"The {{attribute}} must only contain letters.","validation.alpha_dash":"The {{attribute}} must only contain letters, numbers, dashes and underscores.","validation.alpha_num":"The {{attribute}} must only contain letters and numbers.","validation.array":"The {{attribute}} must be an array.","validation.ascii":"The {{attribute}} must only contain single-byte alphanumeric characters and symbols.","validation.before":"The {{attribute}} must be a date before {{date}}.","validation.before_or_equal":"The {{attribute}} must be a date before or equal to {{date}}.","validation.between.array":"The {{attribute}} must have between {{min}} and {{max}} items.","validation.between.file":"The {{attribute}} must be between {{min}} and {{max}} kilobytes.","validation.between.numeric":"The {{attribute}} must be between {{min}} and {{max}}.","validation.between.string":"The {{attribute}} must be between {{min}} and {{max}} characters.","validation.boolean":"The {{attribute}} field must be true or false.","validation.confirmed":"The {{attribute}} confirmation does not match.","validation.current_password":"The password is incorrect.","validation.date":"The {{attribute}} is not a valid date.","validation.date_equals":"The {{attribute}} must be a date equal to {{date}}.","validation.date_format":"The {{attribute}} does not match the format {{format}}.","validation.decimal":"The {{attribute}} must have {{decimal}} decimal places.","validation.declined":"The {{attribute}} must be declined.","validation.declined_if":"The {{attribute}} must be declined when {{other}} is {{value}}.","validation.different":"The {{attribute}} and {{other}} must be different.","validation.digits":"The {{attribute}} must be {{digits}} digits.","validation.digits_between":"The {{attribute}} must be between {{min}} and {{max}} digits.","validation.dimensions":"The {{attribute}} has invalid image dimensions.","validation.distinct":"The {{attribute}} field has a duplicate value.","validation.doesnt_end_with":"The {{attribute}} may not end with one of the following: {{values}}.","validation.doesnt_start_with":"The {{attribute}} may not start with one of the following: {{values}}.","validation.email":"The {{attribute}} must be a valid email address.","validation.ends_with":"The {{attribute}} must end with one of the following: {{values}}.","validation.enum":"The selected {{attribute}} is invalid.","validation.exists":"The selected {{attribute}} is invalid.","validation.file":"The {{attribute}} must be a file.","validation.filled":"The {{attribute}} field must have a value.","validation.gt.array":"The {{attribute}} must have more than {{value}} items.","validation.gt.file":"The {{attribute}} must be greater than {{value}} kilobytes.","validation.gt.numeric":"The {{attribute}} must be greater than {{value}}.","validation.gt.string":"The {{attribute}} must be greater than {{value}} characters.","validation.gte.array":"The {{attribute}} must have {{value}} items or more.","validation.gte.file":"The {{attribute}} must be greater than or equal to {{value}} kilobytes.","validation.gte.numeric":"The {{attribute}} must be greater than or equal to {{value}}.","validation.gte.string":"The {{attribute}} must be greater than or equal to {{value}} characters.","validation.image":"The {{attribute}} must be an image.","validation.in":"The selected {{attribute}} is invalid.","validation.in_array":"The {{attribute}} field does not exist in {{other}}.","validation.integer":"The {{attribute}} must be an integer.","validation.ip":"The {{attribute}} must be a valid IP address.","validation.ipv4":"The {{attribute}} must be a valid IPv4 address.","validation.ipv6":"The {{attribute}} must be a valid IPv6 address.","validation.json":"The {{attribute}} must be a valid JSON string.","validation.lowercase":"The {{attribute}} must be lowercase.","validation.lt.array":"The {{attribute}} must have less than {{value}} items.","validation.lt.file":"The {{attribute}} must be less than {{value}} kilobytes.","validation.lt.numeric":"The {{attribute}} must be less than {{value}}.","validation.lt.string":"The {{attribute}} must be less than {{value}} characters.","validation.lte.array":"The {{attribute}} must not have more than {{value}} items.","validation.lte.file":"The {{attribute}} must be less than or equal to {{value}} kilobytes.","validation.lte.numeric":"The {{attribute}} must be less than or equal to {{value}}.","validation.lte.string":"The {{attribute}} must be less than or equal to {{value}} characters.","validation.mac_address":"The {{attribute}} must be a valid MAC address.","validation.max.array":"The {{attribute}} must not have more than {{max}} items.","validation.max.file":"The {{attribute}} must not be greater than {{max}} kilobytes.","validation.max.numeric":"The {{attribute}} must not be greater than {{max}}.","validation.max.string":"The {{attribute}} must not be greater than {{max}} characters.","validation.max_digits":"The {{attribute}} must not have more than {{max}} digits.","validation.mimes":"The {{attribute}} must be a file of type: {{values}}.","validation.mimetypes":"The {{attribute}} must be a file of type: {{values}}.","validation.min.array":"The {{attribute}} must have at least {{min}} items.","validation.min.file":"The {{attribute}} must be at least {{min}} kilobytes.","validation.min.numeric":"The {{attribute}} must be at least {{min}}.","validation.min.string":"The {{attribute}} must be at least {{min}} characters.","validation.min_digits":"The {{attribute}} must have at least {{min}} digits.","validation.missing":"The {{attribute}} field must be missing.","validation.missing_if":"The {{attribute}} field must be missing when {{other}} is {{value}}.","validation.missing_unless":"The {{attribute}} field must be missing unless {{other}} is {{value}}.","validation.missing_with":"The {{attribute}} field must be missing when {{values}} is present.","validation.missing_with_all":"The {{attribute}} field must be missing when {{values}} are present.","validation.multiple_of":"The {{attribute}} must be a multiple of {{value}}.","validation.not_in":"The selected {{attribute}} is invalid.","validation.not_regex":"The {{attribute}} format is invalid.","validation.numeric":"The {{attribute}} must be a number.","validation.password.letters":"The {{attribute}} must contain at least one letter.","validation.password.mixed":"The {{attribute}} must contain at least one uppercase and one lowercase letter.","validation.password.numbers":"The {{attribute}} must contain at least one number.","validation.password.symbols":"The {{attribute}} must contain at least one symbol.","validation.password.uncompromised":"The given {{attribute}} has appeared in a data leak. Please choose a different {{attribute}}.","validation.present":"The {{attribute}} field must be present.","validation.prohibited":"The {{attribute}} field is prohibited.","validation.prohibited_if":"The {{attribute}} field is prohibited when {{other}} is {{value}}.","validation.prohibited_unless":"The {{attribute}} field is prohibited unless {{other}} is in {{values}}.","validation.prohibits":"The {{attribute}} field prohibits {{other}} from being present.","validation.regex":"The {{attribute}} format is invalid.","validation.required":"The {{attribute}} field is required.","validation.required_array_keys":"The {{attribute}} field must contain entries for: {{values}}.","validation.required_if":"The {{attribute}} field is required when {{other}} is {{value}}.","validation.required_if_accepted":"The {{attribute}} field is required when {{other}} is accepted.","validation.required_unless":"The {{attribute}} field is required unless {{other}} is in {{values}}.","validation.required_with":"The {{attribute}} field is required when {{values}} is present.","validation.required_with_all":"The {{attribute}} field is required when {{values}} are present.","validation.required_without":"The {{attribute}} field is required when {{values}} is not present.","validation.required_without_all":"The {{attribute}} field is required when none of {{values}} are present.","validation.same":"The {{attribute}} and {{other}} must match.","validation.size.array":"The {{attribute}} must contain {{size}} items.","validation.size.file":"The {{attribute}} must be {{size}} kilobytes.","validation.size.numeric":"The {{attribute}} must be {{size}}.","validation.size.string":"The {{attribute}} must be {{size}} characters.","validation.starts_with":"The {{attribute}} must start with one of the following: {{values}}.","validation.string":"The {{attribute}} must be a string.","validation.timezone":"The {{attribute}} must be a valid timezone.","validation.unique":"The {{attribute}} has already been taken.","validation.uploaded":"The {{attribute}} failed to upload.","validation.uppercase":"The {{attribute}} must be uppercase.","validation.url":"The {{attribute}} must be a valid URL.","validation.ulid":"The {{attribute}} must be a valid ULID.","validation.uuid":"The {{attribute}} must be a valid UUID.","validation.custom.attribute-name.rule-name":"custom-message","validation.unsupported_currency_code":"The currency code you provided is invalid or not supported.","validation.unsupported_period":"The period you provided is invalid or not supported.","validation.unsupported_token_symbol":"The token symbol you provided is invalid or not supported.","validation.gallery_title_required":"Gallery name is required.","validation.gallery_title_max_characters":"The gallery name should not exceed 50 characters.","validation.gallery_title_invalid":"The gallery name is invalid.","validation.nfts_required":"Please add at least one NFT.","validation.nfts_max_size":"Galleries can contain no more than {{limit}} NFTs","validation.invalid_nfts":"The NFT in position {{position}} is invalid, please select another one.","validation.invalid_cover":"You have selected an invalid cover image, please try another one."} \ No newline at end of file From 02b39f4a79fdb4248c612f600b5f51407fd239fb Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 17:25:36 +0400 Subject: [PATCH 32/35] fix tests --- resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts index 4a8552586..3ed9e7cce 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryDrafts.test.ts @@ -98,7 +98,7 @@ describe("useGalleryDrafts", () => { }); }); - it("should try to create a new row if draft hasn't been created yet", async () => { + it("should try to create a new record if draft hasn't been created yet", async () => { mocks.useIndexedDB().add.mockResolvedValue(2); const { result } = renderHook(() => useGalleryDrafts()); @@ -112,7 +112,7 @@ describe("useGalleryDrafts", () => { }); }); - it("should not create a new row if disabled", async () => { + it("should not create a new draft if disabled", async () => { mocks.useIndexedDB().add.mockResolvedValue(2); const { result } = renderHook(() => useGalleryDrafts(undefined, true)); From cde31a21c2d2a6c2533f8d6225bc454435e03635 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 17:34:01 +0400 Subject: [PATCH 33/35] mip --- .../GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx index ce05eb9ae..ea68b2054 100644 --- a/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx +++ b/resources/js/Components/Galleries/GalleryPage/GalleryActionToolbar/GalleryDraftStatus.tsx @@ -35,7 +35,6 @@ export const GalleryDraftStatus = ({ className="text-theme-secondary-700 dark:text-theme-dark-200" />
- Saving to draft {t("pages.galleries.create.saving_to_draft")}
From 267323dd40fefbb0fe0f1a30af315a72f18a3035 Mon Sep 17 00:00:00 2001 From: Shahin Safaraliyev Date: Thu, 2 Nov 2023 19:42:54 +0400 Subject: [PATCH 34/35] wip --- resources/js/Pages/Galleries/MyGalleries/Create.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 0d57b3126..4f7ba6ab3 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -204,7 +204,6 @@ const Create = ({ setShowDeleteModal(true); }} onCancel={() => { - setDraftTitle(data.name); router.visit(route("my-galleries")); }} onPublish={publishHandler} From cdda34b21a3524150f1f9ca63b9320284198ad0e Mon Sep 17 00:00:00 2001 From: shahin-hq <132887516+shahin-hq@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:27:07 +0400 Subject: [PATCH 35/35] feat: handle gallery drafts toolbar actions (#346) --- resources/js/Pages/Galleries/MyGalleries/Create.tsx | 6 +++++- resources/js/Pages/Galleries/hooks/useGalleryForm.ts | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/resources/js/Pages/Galleries/MyGalleries/Create.tsx b/resources/js/Pages/Galleries/MyGalleries/Create.tsx index 4f7ba6ab3..a506e4c7e 100644 --- a/resources/js/Pages/Galleries/MyGalleries/Create.tsx +++ b/resources/js/Pages/Galleries/MyGalleries/Create.tsx @@ -62,7 +62,7 @@ const Create = ({ const { draftId } = getQueryParameters(); - const { setDraftCover, setDraftNfts, setDraftTitle, draft, isSaving } = useGalleryDrafts( + const { setDraftCover, setDraftNfts, setDraftTitle, draft, isSaving, deleteDraft } = useGalleryDrafts( isTruthy(draftId) ? Number(draftId) : undefined, isTruthy(gallery?.slug), ); @@ -76,6 +76,10 @@ const Create = ({ const { selectedNfts, data, setData, errors, submit, updateSelectedNfts, processing } = useGalleryForm({ gallery, setDraftNfts, + deleteDraft: (): void => { + void deleteDraft(); + replaceUrlQuery({ draftId: "" }); + }, }); const totalValue = 0; diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts index 206131755..62930b23f 100644 --- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts +++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts @@ -14,9 +14,11 @@ interface UseGalleryFormProperties extends Record { export const useGalleryForm = ({ gallery, setDraftNfts, + deleteDraft, }: { gallery?: App.Data.Gallery.GalleryData; setDraftNfts?: (nfts: App.Data.Gallery.GalleryNftData[]) => void; + deleteDraft?: () => void; }): { selectedNfts: App.Data.Gallery.GalleryNftData[]; gallery?: App.Data.Gallery.GalleryData; @@ -83,6 +85,9 @@ export const useGalleryForm = ({ type: "error", }); }, + onSuccess: () => { + deleteDraft?.(); + }, }); };