diff --git a/resources/js/Components/Drafts/NftGalleryDraftCard.test.tsx b/resources/js/Components/Drafts/NftGalleryDraftCard.test.tsx
index 713cc2bd7..9c5f7ca28 100644
--- a/resources/js/Components/Drafts/NftGalleryDraftCard.test.tsx
+++ b/resources/js/Components/Drafts/NftGalleryDraftCard.test.tsx
@@ -19,9 +19,9 @@ describe("NftGalleryDraftCard", () => {
coverType: null,
coverFileName: null,
walletAddress: "0x22Fd644149ea87ca26237183ad6A66f91dfcFB87",
+ collectionsCount: 1,
nfts: [],
value: "0",
- collectionsCount: 0,
updatedAt: 123,
};
diff --git a/resources/js/Components/Drafts/NftGalleryDraftCard.tsx b/resources/js/Components/Drafts/NftGalleryDraftCard.tsx
index fc6e75a43..291946aa3 100644
--- a/resources/js/Components/Drafts/NftGalleryDraftCard.tsx
+++ b/resources/js/Components/Drafts/NftGalleryDraftCard.tsx
@@ -21,8 +21,7 @@ export const NftGalleryDraftCard = ({
return (
=> {
+ const redirectToNewDraft = async (existingDraft: GalleryDraftUnsaved): Promise => {
try {
const newDraft = await add({ ...existingDraft, walletAddress: auth.wallet?.address, nfts: [] });
reset(newDraft);
diff --git a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts
index a3a812446..f9890229d 100644
--- a/resources/js/Pages/Galleries/hooks/useGalleryForm.ts
+++ b/resources/js/Pages/Galleries/hooks/useGalleryForm.ts
@@ -1,7 +1,7 @@
import { useForm } from "@inertiajs/react";
import { type FormEvent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { type GalleryDraft } from "./useWalletDraftGalleries";
+import { type GalleryDraftUnsaved } from "./useWalletDraftGalleries";
import { useToasts } from "@/Hooks/useToasts";
import { arrayBufferToFile } from "@/Utils/array-buffer-to-file";
import { isTruthy } from "@/Utils/is-truthy";
@@ -20,7 +20,7 @@ export const useGalleryForm = ({
deleteDraft,
}: {
gallery?: App.Data.Gallery.GalleryData;
- draft?: GalleryDraft;
+ draft?: GalleryDraftUnsaved;
setDraftNfts?: (nfts: App.Data.Gallery.GalleryNftData[]) => void;
deleteDraft?: () => void;
}): {
diff --git a/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.test.ts b/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.test.ts
index fdf2b4b14..81c3a2c65 100644
--- a/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.test.ts
+++ b/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.test.ts
@@ -1,6 +1,6 @@
import { act } from "react-dom/test-utils";
import { expect } from "vitest";
-import { type GalleryDraft, useWalletDraftGalleries } from "./useWalletDraftGalleries";
+import { type GalleryDraft, type GalleryDraftUnsaved, useWalletDraftGalleries } from "./useWalletDraftGalleries";
import { renderHook, waitFor } from "@/Tests/testing-library";
const defaultGalleryDraft = {
@@ -12,12 +12,12 @@ const defaultGalleryDraft = {
nfts: [],
walletAddress: "mockedAddress",
value: "test",
- collectionsCount: 1,
updatedAt: new Date().getTime(),
+ collectionsCount: 1,
};
const expiredGalleryDraft = {
- id: null,
+ id: undefined,
title: "",
cover: null,
coverType: null,
@@ -25,8 +25,8 @@ const expiredGalleryDraft = {
nfts: [],
walletAddress: "mockedAddress",
value: "test",
- collectionsCount: 1,
updatedAt: 169901639000,
+ collectionsCount: 1,
};
interface IndexedDBMockResponse {
@@ -41,15 +41,15 @@ interface IndexedDBMockResponse {
}
const useIndexedDBMock = (): IndexedDBMockResponse => {
- const drafts: GalleryDraft[] = [defaultGalleryDraft, expiredGalleryDraft];
+ const drafts: GalleryDraft | GalleryDraftUnsaved[] = [defaultGalleryDraft, expiredGalleryDraft];
return {
- add: async (draft: GalleryDraft): Promise => {
+ add: async (draft: GalleryDraftUnsaved): Promise => {
const id = drafts.length + 1;
drafts.push({ ...draft, id });
return await Promise.resolve(id);
},
- getAll: async (): Promise => await Promise.resolve(drafts),
+ getAll: async (): Promise => (await Promise.resolve(drafts)) as GalleryDraft[],
update: async (draft: GalleryDraft): Promise => {
const index = drafts.findIndex((savedDraft) => savedDraft.id === draft.id);
drafts.splice(index, 1, draft);
@@ -62,7 +62,8 @@ const useIndexedDBMock = (): IndexedDBMockResponse => {
await Promise.resolve();
},
- getByID: async (id: number | null) => await Promise.resolve(drafts.find((draft) => draft.id === id)),
+ getByID: async (id: number | null) =>
+ (await Promise.resolve(drafts.find((draft) => draft.id === id))) as GalleryDraft,
openCursor: vi.fn(),
getByIndex: vi.fn(),
clear: vi.fn(),
@@ -98,7 +99,6 @@ describe("useWalletDraftGalleries", () => {
nfts: [],
walletAddress: "mockedAddress",
value: "test",
- collectionsCount: 1,
updatedAt: new Date().getTime(),
});
});
@@ -131,7 +131,6 @@ describe("useWalletDraftGalleries", () => {
nfts: [],
walletAddress: "mockedAddress",
value: "test",
- collectionsCount: 1,
updatedAt: new Date().getTime(),
});
});
diff --git a/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.ts b/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.ts
index 169f90046..bd10e9b2b 100644
--- a/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.ts
+++ b/resources/js/Pages/Galleries/hooks/useWalletDraftGalleries.ts
@@ -1,3 +1,4 @@
+import uniqBy from "lodash/uniqBy";
import { useCallback, useEffect, useState } from "react";
import { useIndexedDB } from "react-indexed-db-hook";
import { isTruthy } from "@/Utils/is-truthy";
@@ -16,7 +17,7 @@ export interface DraftNft {
collectionSlug: string;
}
-export interface GalleryDraft {
+export interface GalleryDraftUnsaved {
title: string;
cover: ArrayBuffer | null;
coverType: string | null;
@@ -24,28 +25,47 @@ export interface GalleryDraft {
walletAddress?: string;
id?: number | null;
value: string | null;
- collectionsCount: number;
updatedAt: number | null;
coverFileName: string | null;
}
-interface GallerySavedDraft extends GalleryDraft {
+export interface GalleryDraft extends GalleryDraftUnsaved {
id: number;
+ collectionsCount: number;
}
interface WalletDraftGalleriesState {
- upsert: (draft: GalleryDraft) => Promise;
- add: (draft: GalleryDraft) => Promise;
+ upsert: (draft: GalleryDraftUnsaved) => Promise;
+ add: (draft: GalleryDraftUnsaved) => Promise;
remove: (id?: number | null) => Promise;
removeExpired: () => Promise;
drafts: GalleryDraft[];
- findWalletDraftById: (id: number | string) => Promise;
+ findWalletDraftById: (id: number | string) => Promise;
isLoading: boolean;
isSaving: boolean;
hasReachedLimit: boolean;
- allDrafts: () => Promise;
+ allDrafts: () => Promise;
}
+/**
+ * Calculate collections count based on saved nfts.
+ *
+ * @param {GalleryDraft} draft
+ * @returns {number}
+ */
+const calculateCollectionsCount = (draft: GalleryDraftUnsaved): number => uniqBy(draft.nfts, "collectionSlug").length;
+
+/**
+ * Determine if gallery is expired.
+ *
+ * @param {GalleryDraft} draft
+ * @returns {boolean}
+ */
+const isExpired = (draft: GalleryDraft): boolean => {
+ const thresholdDaysAgo = new Date().getTime() - DRAFT_TTL_DAYS * 86400 * 1000;
+ return (draft.updatedAt ?? 0) < thresholdDaysAgo;
+};
+
/**
* Note: The react-indexed-db-hook package that is used under the hood in this hook
* is not reactive. That means that if this hook is used in multiple components,
@@ -84,8 +104,9 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
* @param {GalleryDraft} draft
* @returns {Promise}
*/
- const add = async (draft: GalleryDraft): Promise => {
+ const add = async (draft: GalleryDraftUnsaved): Promise => {
const allDraftsCount = await allDrafts();
+
if (allDraftsCount.length >= MAX_DRAFT_LIMIT_PER_WALLET) {
throw new Error("[useWalletDraftGalleries:upsert] Reached limit");
}
@@ -97,6 +118,7 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
const id = await database.add({
...draftToSave,
updatedAt: new Date().getTime(),
+ collectionsCount: calculateCollectionsCount(draft),
});
setIsSaving(false);
@@ -110,7 +132,7 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
* @param {GalleryDraft} draft
* @returns {Promise}
*/
- const update = async (draft: GalleryDraft): Promise => {
+ const update = async (draft: GalleryDraftUnsaved): Promise => {
if (!isTruthy(draft.id)) {
throw new Error("[useWalletDraftGalleries:update] Missing Id");
}
@@ -120,6 +142,7 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
await database.update({
...draft,
updatedAt: new Date().getTime(),
+ collectionsCount: calculateCollectionsCount(draft),
});
setIsSaving(false);
@@ -133,7 +156,7 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
* @param {GalleryDraft} draft
* @returns {Promise}
*/
- const upsert = async (draft: GalleryDraft): Promise => {
+ const upsert = async (draft: GalleryDraftUnsaved): Promise => {
if (isTruthy(draft.id)) {
return await update(draft);
}
@@ -174,8 +197,8 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
*
* @returns {Promise}
*/
- const allDrafts = async (): Promise => {
- const allDrafts: GallerySavedDraft[] = await database.getAll();
+ const allDrafts = async (): Promise => {
+ const allDrafts: GalleryDraft[] = await database.getAll();
return allDrafts.filter(
(draft) => draft.walletAddress?.toLowerCase() === address.toLowerCase() && !isExpired(draft),
);
@@ -187,8 +210,8 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
* @param {number} id
* @returns {Promise}
*/
- const findWalletDraftById = async (id: number | string): Promise => {
- const draft: GallerySavedDraft | undefined = await database.getByID(Number(id));
+ const findWalletDraftById = async (id: number | string): Promise => {
+ const draft: GalleryDraft | undefined = await database.getByID(Number(id));
if (draft?.walletAddress?.toLowerCase() !== address.toLowerCase()) {
return undefined;
@@ -197,24 +220,13 @@ export const useWalletDraftGalleries = ({ address }: Properties): WalletDraftGal
return draft;
};
- /**
- * Determine if gallery is expired.
- *
- * @param {GalleryDraft} draft
- * @returns {boolean}
- */
- const isExpired = (draft: GalleryDraft): boolean => {
- const thresholdDaysAgo = new Date().getTime() - DRAFT_TTL_DAYS * 86400 * 1000;
- return (draft.updatedAt ?? 0) < thresholdDaysAgo;
- };
-
/**
* Find draft or throw. Used internally for add/remove.
*
* @param {number | string} id
* @returns {Promise}
*/
- const findByIdOrThrow = async (id: number | string): Promise => {
+ const findByIdOrThrow = async (id: number | string): Promise => {
const draft = await findWalletDraftById(id);
if (!isTruthy(draft)) {
diff --git a/resources/js/Pages/Galleries/hooks/useWalletDraftGallery.ts b/resources/js/Pages/Galleries/hooks/useWalletDraftGallery.ts
index d6ff0fc04..1c497895f 100644
--- a/resources/js/Pages/Galleries/hooks/useWalletDraftGallery.ts
+++ b/resources/js/Pages/Galleries/hooks/useWalletDraftGallery.ts
@@ -1,14 +1,14 @@
import { useEffect, useState } from "react";
-import { type GalleryDraft, useWalletDraftGalleries } from "./useWalletDraftGalleries";
+import { type GalleryDraftUnsaved, useWalletDraftGalleries } from "./useWalletDraftGalleries";
import { isTruthy } from "@/Utils/is-truthy";
interface WalletDraftGalleryState {
isSaving: boolean;
- draft: GalleryDraft;
+ draft: GalleryDraftUnsaved;
setCover: (image: ArrayBuffer | null, name: string | null, type: string | null) => void;
setNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void;
setTitle: (title: string) => void;
- reset: (draft?: Partial) => void;
+ reset: (draft?: Partial) => void;
isLoading: boolean;
}
@@ -36,7 +36,7 @@ export const useWalletDraftGallery = ({
const [isLoading, setIsLoading] = useState(true);
const { upsert, findWalletDraftById, isSaving } = useWalletDraftGalleries({ address });
- const [draft, setDraft] = useState(defaultDraft);
+ const [draft, setDraft] = useState(defaultDraft);
useEffect(() => {
if (draftId === undefined || isDisabled === true) {
@@ -59,7 +59,7 @@ export const useWalletDraftGallery = ({
void getDraft();
}, [draftId, address]);
- const saveDraft = async (draft: GalleryDraft): Promise => {
+ const saveDraft = async (draft: GalleryDraftUnsaved): Promise => {
if (isTruthy(isDisabled)) {
setIsLoading(false);
return;
@@ -82,7 +82,7 @@ export const useWalletDraftGallery = ({
void saveDraft({ ...draft, title });
};
- const reset = (draft?: Partial): void => {
+ const reset = (draft?: Partial): void => {
setDraft({ ...defaultDraft, ...draft });
};