diff --git a/src/app/page.tsx b/src/app/page.tsx index 4b6725f0..d3e94c3d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -34,7 +34,7 @@ function CanvasPageContent() { useEffect(() => { if (token || getStoredLoginToken()) { - void fetchUserInfo(token); + void fetchUserInfo(token, { forceRefresh: true }); } }, [fetchUserInfo, token]); diff --git a/src/components/__tests__/ImageInputNode.test.tsx b/src/components/__tests__/ImageInputNode.test.tsx index 1913f7a7..64f2f19e 100644 --- a/src/components/__tests__/ImageInputNode.test.tsx +++ b/src/components/__tests__/ImageInputNode.test.tsx @@ -400,6 +400,27 @@ describe("ImageInputNode", () => { expect(screen.getByLabelText("Remove image")).toBeInTheDocument(); }); + it("should hide unavailable image editing and asset actions", () => { + render( + + + + ); + + expect(screen.queryByLabelText("Choose from assets")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Crop")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Draw")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Multi-angle")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Enhance")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Lighting")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Repaint")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Grid")).not.toBeInTheDocument(); + expect(screen.getAllByLabelText("Replace")).toHaveLength(2); + expect(screen.getByLabelText("Download image")).toBeInTheDocument(); + expect(screen.getAllByLabelText("Open preview")).toHaveLength(2); + expect(screen.getByLabelText("Remove image")).toBeInTheDocument(); + }); + it("should call updateNodeData to clear image when remove button is clicked", () => { render( @@ -558,7 +579,7 @@ describe("ImageInputNode", () => { const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; const clickSpy = vi.spyOn(fileInput, "click"); - fireEvent.click(screen.getByLabelText("Replace")); + fireEvent.click(screen.getAllByLabelText("Replace")[0]); expect(clickSpy).toHaveBeenCalled(); }); diff --git a/src/components/nodes/ImageInputNode.tsx b/src/components/nodes/ImageInputNode.tsx index f89adadc..6ba094db 100644 --- a/src/components/nodes/ImageInputNode.tsx +++ b/src/components/nodes/ImageInputNode.tsx @@ -43,14 +43,6 @@ function EmptyImageIcon() { } type ImageToolbarIconKind = - | "crop" - | "draw" - | "multiAngle" - | "enhance" - | "lighting" - | "repaint" - | "grid" - | "folder" | "download" | "focus" | "delete" @@ -66,22 +58,6 @@ function ImageToolbarIcon({ kind }: { kind: ImageToolbarIconKind }) { }; switch (kind) { - case "crop": - return ; - case "draw": - return ; - case "multiAngle": - return ; - case "enhance": - return ; - case "lighting": - return ; - case "repaint": - return ; - case "grid": - return ; - case "folder": - return ; case "download": return ; case "focus": @@ -93,23 +69,6 @@ function ImageToolbarIcon({ kind }: { kind: ImageToolbarIconKind }) { } } -function ToolbarDivider() { - return ; -} - -function StaticImageToolbarItem({ label, icon }: { label: string; icon: ImageToolbarIconKind }) { - return ( - - - {label} - - ); -} - export function ImageInputNode({ id, data, selected }: NodeProps) { const { t } = useI18n(); const nodeData = data; @@ -274,43 +233,23 @@ export function ImageInputNode({ id, data, selected }: NodeProps {t("imageInput.localUpload")} - { - e.preventDefault(); - e.stopPropagation(); - }} - aria-label={t("imageInput.fromAssets")} - title={t("imageInput.fromAssets")} - className="flex h-14 items-center gap-3 whitespace-nowrap rounded-xl border border-neutral-600/70 bg-neutral-800 px-5 text-xl text-neutral-200 shadow-xl shadow-black/30 transition-colors hover:border-neutral-500 hover:bg-neutral-700 hover:text-neutral-100" - > - - {t("imageInput.fromAssets")} - )} {selected && hasImage && ( - - - - - - - - - + { e.preventDefault(); e.stopPropagation(); + handleOpenFileDialog(); }} - aria-label={t("imageInput.fromAssets")} - title={t("imageInput.fromAssets")} + aria-label={t("imageInput.replace")} + title={t("imageInput.replace")} className="flex h-10 items-center justify-center rounded-md px-2 text-neutral-300 transition-colors hover:bg-neutral-700 hover:text-neutral-100" > - + { expect(useUserStore.getState().gatewayApiKey).toBe("manual-gateway-key"); expect(openLoginModal).not.toHaveBeenCalled(); }); + + it("refreshes user info when forceRefresh is enabled", async () => { + const cachedUser = { ...user, allCoins: 10 }; + const refreshedUser = { ...user, allCoins: 99 }; + useUserStore.setState({ + token: "stored-token", + userInfo: cachedUser, + gatewayApiKey: "stored-gateway-key", + }); + + const fetcher = vi.fn().mockResolvedValueOnce( + new Response( + JSON.stringify({ + data: { + token: "stored-token", + user: refreshedUser, + }, + message: "ok", + status: "0000", + }), + { status: 200 } + ) + ); + vi.stubGlobal("fetch", fetcher); + + await expect( + useUserStore.getState().fetchUserInfo("", { forceRefresh: true }) + ).resolves.toEqual(refreshedUser); + + expect(fetcher).toHaveBeenCalledWith(USER_INFO_ENDPOINT, { + method: "GET", + headers: { + Accept: "application/json", + Authorization: "Bearer stored-token", + token: "stored-token", + }, + }); + expect(useUserStore.getState().userInfo?.allCoins).toBe(99); + }); }); diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 35e7b58a..76932853 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -174,7 +174,7 @@ export const useUserStore = create()( isLoadingGatewayApiKey: false, gatewayApiKeyError: null, }), - fetchUserInfo: async (token) => { + fetchUserInfo: async (token, options) => { const normalizedToken = resolveLoginToken(token, get().token); if (!normalizedToken) { set({ @@ -188,7 +188,11 @@ export const useUserStore = create()( } const current = get(); - if (current.token === normalizedToken && current.userInfo) { + if ( + !options?.forceRefresh && + current.token === normalizedToken && + current.userInfo + ) { if (!current.gatewayApiKey) { await get().fetchGatewayApiKey(); } diff --git a/src/types/user.ts b/src/types/user.ts index 74b945d8..6c2e2f69 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -65,7 +65,10 @@ export interface UserStoreState { gatewayApiKeyError: string | null; setToken: (token: string | null) => void; setGatewayApiKey: (apiKey: string | null) => void; - fetchUserInfo: (token: string) => Promise; + fetchUserInfo: ( + token: string, + options?: { forceRefresh?: boolean } + ) => Promise; fetchGatewayApiKey: () => Promise; clearGatewayApiKey: () => void; clearSession: () => void;