Skip to content

Commit 4b6d5a8

Browse files
authored
Marketplace toasts (#274)
* chore: update @cartridge/ui, controller dependencies * feat: added <ControllerToaster> * chore: unlist calls contract directly * feat: auto close controller (placeholder)
1 parent 16a0d83 commit 4b6d5a8

File tree

10 files changed

+220
-76
lines changed

10 files changed

+220
-76
lines changed

client/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@cartridge/penpal": "^6.2.3",
3232
"@cartridge/presets": "catalog:",
3333
"@cartridge/ui": "catalog:",
34+
"@dojoengine/core": "catalog:",
3435
"@dojoengine/grpc": "catalog:",
3536
"@dojoengine/react": "catalog:",
3637
"@dojoengine/sdk": "catalog:",
@@ -74,7 +75,7 @@
7475
"react-player": "^2.16.0",
7576
"react-router-dom": "^6.27.0",
7677
"react-youtube": "^10.1.0",
77-
"sonner": "^1.4.41",
78+
"sonner": "^2.0.7",
7879
"starknet": "catalog:",
7980
"tailwind-merge": "^3.0.2",
8081
"tailwindcss-animate": "^1.0.7",
@@ -121,4 +122,4 @@
121122
"@storybook/addon-docs": "^10.2.8"
122123
},
123124
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
124-
}
125+
}

client/src/components/ui/marketplace/token-detail/TokenFooterActions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { OrderModel } from "@cartridge/arcade";
33
import { useAtomValue } from "@effect-atom/atom-react";
44
import { orderWithUsdAtom } from "@/effect/atoms/marketplace";
55
import { PriceFooter } from "../../modules/price-footer";
6+
import { useControllerCloseAfterToast } from "@/hooks/controller";
67

78
interface TokenFooterActionsProps {
89
isOwner: boolean;
@@ -76,6 +77,7 @@ export function TokenFooterActions({
7677
handleSend,
7778
}: TokenFooterActionsProps) {
7879
const orderWithUsd = useAtomValue(orderWithUsdAtom(order));
80+
useControllerCloseAfterToast();
7981

8082
if (!isOwner && !isListed) {
8183
return null;

client/src/features/marketplace/items/useMarketplaceItemsViewModel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export function useMarketplaceItemsViewModel({
308308

309309
const handlePurchaseCallback = useHandlePurchaseCallback(handlerParams);
310310
const handleListCallback = useHandleListCallback(handlerParams);
311-
const handleUnlistCallback = useHandleUnlistCallback(handlerParams);
311+
const handleUnlistCallback = useHandleUnlistCallback();
312312
const handleSendCallback = useHandleSendCallback(handlerParams);
313313

314314
const handlePurchase = useCallback(
@@ -335,6 +335,7 @@ export function useMarketplaceItemsViewModel({
335335
handleUnlistCallback(
336336
collectionAddress,
337337
tokens.map((token) => token.token_id ?? "").filter(Boolean),
338+
tokens.map((token) => token.orders[0].order),
338339
),
339340
[collectionAddress, handleUnlistCallback],
340341
);

client/src/features/marketplace/token-detail/useTokenDetailViewModel.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
useHandleListCallback,
1919
useHandleSendCallback,
2020
useHandleUnlistCallback,
21+
useHandleUnlistViewCallback,
2122
} from "@/hooks/marketplace-actions-handlers";
2223
import { useProject } from "@/hooks/project";
2324

@@ -196,7 +197,8 @@ export function useTokenDetailViewModel({
196197

197198
const handlePurchaseCallback = useHandlePurchaseCallback(handlerParams);
198199
const handleListCallback = useHandleListCallback(handlerParams);
199-
const handleUnlistCallback = useHandleUnlistCallback(handlerParams);
200+
const handleUnlistCallback = useHandleUnlistCallback();
201+
const handleUnlistViewCallback = useHandleUnlistViewCallback(handlerParams);
200202
const handleSendCallback = useHandleSendCallback(handlerParams);
201203

202204
const handlePurchase = useCallback(async () => {
@@ -210,7 +212,11 @@ export function useTokenDetailViewModel({
210212
}, [handleListCallback, collectionAddress, tokenId]);
211213

212214
const handleUnlist = useCallback(async () => {
213-
handleUnlistCallback(collectionAddress, [tokenId]);
215+
if (lowestOrder) {
216+
handleUnlistCallback(collectionAddress, [tokenId], [lowestOrder]);
217+
} else {
218+
handleUnlistViewCallback(collectionAddress, [tokenId]);
219+
}
214220
}, [handleUnlistCallback, collectionAddress, tokenId]);
215221

216222
const handleSend = useCallback(async () => {

client/src/hooks/controller.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { useAccount } from "@starknet-react/core";
5+
import {
6+
CONTROLLER_TOAST_MESSAGE_TYPE,
7+
type ToastOptions,
8+
} from "@cartridge/ui";
9+
import type ControllerConnector from "@cartridge/connector/controller";
10+
11+
export function useControllerUsername(): string | undefined {
12+
const { account, connector } = useAccount();
13+
const [username, setUSername] = useState<string>();
14+
useEffect(() => {
15+
if (account && connector) {
16+
(connector as ControllerConnector)
17+
.username()
18+
?.then((u) => setUSername(u));
19+
}
20+
}, [account, connector]);
21+
return username;
22+
}
23+
24+
export function useControllerCloseAfterToast() {
25+
const { connector } = useAccount();
26+
useEffect(() => {
27+
if (!connector) return;
28+
29+
const eventHandler = (event: any) => {
30+
const options =
31+
event.data.type === CONTROLLER_TOAST_MESSAGE_TYPE
32+
? (event.data.options as ToastOptions)
33+
: undefined;
34+
35+
if (options?.safeToClose) {
36+
const controller = (connector as ControllerConnector)?.controller;
37+
controller?.close();
38+
}
39+
};
40+
41+
window.addEventListener("message", eventHandler);
42+
return () => {
43+
window.removeEventListener("message", eventHandler);
44+
};
45+
}, [connector]);
46+
}

client/src/hooks/marketplace-actions-handlers.ts

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { useCallback } from "react";
22
import { useAccount, useConnect } from "@starknet-react/core";
3+
import { getContractByName } from "@dojoengine/core";
34
import { DEFAULT_PRESET, DEFAULT_PROJECT } from "@/constants";
4-
import { addAddressPadding } from "starknet";
5+
import { addAddressPadding, type Call, CallData } from "starknet";
56
import { useAnalytics } from "@/hooks/useAnalytics";
67
import type ControllerConnector from "@cartridge/connector/controller";
78
import { useConnectionViewModel } from "@/features/connection";
89
import { useTokenContracts } from "@/effect/hooks/tokens";
910
import { CollectionType } from "@/effect/atoms/tokens";
1011
import type { OrderModel } from "@cartridge/arcade";
11-
import { useUsername } from "@/hooks/username";
12+
import { useControllerUsername } from "@/hooks/controller";
13+
import { useArcade } from "./arcade";
1214

1315
export type ActionHandlerParams = {
1416
project?: string;
@@ -21,8 +23,6 @@ export function useHandlePurchaseCallback(
2123
collectionAddress: string,
2224
tokenIds: string[],
2325
orders: OrderModel[],
24-
preset?: string,
25-
project?: string,
2626
) => Promise<void> {
2727
const { address } = useAccount();
2828
const { trackEvent, events } = useAnalytics();
@@ -138,22 +138,47 @@ export function useHandleListViewCallback(
138138
);
139139
}
140140

141-
export function useHandleUnlistCallback(
142-
presetParams?: ActionHandlerParams,
143-
): (collectionAddress: string, tokenIds: string[]) => Promise<void> {
144-
const pathBuilder = useControllerPathBuilder(presetParams);
145-
const openController = useOpenControllerAtPathCallback();
141+
export function useHandleUnlistCallback(): (
142+
collectionAddress: string,
143+
tokenIds: string[],
144+
orders: OrderModel[],
145+
) => Promise<void> {
146+
const { provider } = useArcade();
147+
const openController = useOpenControllerExecuteCallback();
146148
return useCallback(
147-
async (collectionAddress: string, tokenIds: string[]) => {
148-
const path = pathBuilder({
149-
// viewType: "unlist", // TODO: /unlist is not implemented in the controller
150-
viewType: "unlistView",
151-
collectionAddress,
152-
tokenIds,
153-
});
154-
return openController(path);
149+
async (
150+
collectionAddress: string,
151+
tokenIds: string[],
152+
orders: OrderModel[],
153+
) => {
154+
// TODO: /unlist is not implemented in the controller
155+
// const path = pathBuilder({
156+
// viewType: "unlist",
157+
// collectionAddress,
158+
// tokenIds,
159+
// });
160+
161+
// call the execute endpoint directly
162+
const contract = getContractByName(
163+
provider.manifest,
164+
"ARCADE",
165+
"Marketplace",
166+
);
167+
const contractAddress = contract?.address ?? "0x0";
168+
const callData = new CallData(provider.manifest.abis);
169+
const calls: Call[] = orders.map((order, index) => ({
170+
entrypoint: "cancel",
171+
contractAddress,
172+
calldata: callData.compile("cancel", [
173+
order.id,
174+
collectionAddress,
175+
`0x${tokenIds[index]}`,
176+
]),
177+
}));
178+
179+
return openController(calls);
155180
},
156-
[pathBuilder, openController],
181+
[openController],
157182
);
158183
}
159184

@@ -233,7 +258,7 @@ function useControllerPathBuilder(
233258
): (params: MakeControllerViewPathParams) => string | undefined {
234259
const { trackEvent, events } = useAnalytics();
235260

236-
const username = useUsername();
261+
const username = useControllerUsername();
237262

238263
const collections = useTokenContracts();
239264

@@ -348,3 +373,35 @@ export function useOpenControllerAtPathCallback(): (
348373

349374
return callback;
350375
}
376+
377+
export function useOpenControllerExecuteCallback(): (
378+
calls: Call[],
379+
) => Promise<void> {
380+
const { connector } = useConnect();
381+
const { isConnected } = useAccount();
382+
const { onConnect, isConnectDisabled } = useConnectionViewModel();
383+
384+
const callback = useCallback(
385+
async (calls: Call[]) => {
386+
if (!calls || calls.length === 0) return;
387+
388+
if (!isConnected) {
389+
if (isConnectDisabled) {
390+
return;
391+
}
392+
await onConnect();
393+
}
394+
395+
const controller = (connector as ControllerConnector)?.controller;
396+
if (!controller) {
397+
console.error("Connector not initialized");
398+
return;
399+
}
400+
401+
controller.openExecute(calls);
402+
},
403+
[connector, isConnected, isConnectDisabled],
404+
);
405+
406+
return callback;
407+
}

client/src/hooks/username.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +0,0 @@
1-
import { useEffect, useState } from "react";
2-
import { useAccount } from "@starknet-react/core";
3-
import type ControllerConnector from "@cartridge/connector/controller";
4-
5-
export function useUsername(): string | undefined {
6-
const { account, connector } = useAccount();
7-
const [username, setUSername] = useState<string>();
8-
useEffect(() => {
9-
if (account && connector) {
10-
(connector as ControllerConnector)
11-
.username()
12-
?.then((u) => setUSername(u));
13-
}
14-
}, [account, connector]);
15-
16-
return username;
17-
}

client/src/routes/__root.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useMemo } from "react";
22
import { Outlet, createRootRoute, useMatches } from "@tanstack/react-router";
33
import { Template } from "@/components/template";
4-
import { SonnerToaster } from "@cartridge/ui";
4+
import { ControllerToaster } from "@cartridge/ui";
55
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
66
import { useNavigationContext } from "@/features/navigation";
77
import { useArcade } from "@/hooks/arcade";
@@ -35,9 +35,9 @@ function RootComponent() {
3535
<Outlet />
3636
</Template>
3737
)}
38-
<SonnerToaster position="top-center" />
38+
<ControllerToaster position="bottom-right" />
3939
{import.meta.env.DEV ? (
40-
<TanStackRouterDevtools position="bottom-right" />
40+
<TanStackRouterDevtools position="bottom-left" />
4141
) : null}
4242
</>
4343
);

0 commit comments

Comments
 (0)