diff --git a/packages/controller/src/controller.ts b/packages/controller/src/controller.ts index 9c6be2afa..4d6779737 100644 --- a/packages/controller/src/controller.ts +++ b/packages/controller/src/controller.ts @@ -79,9 +79,9 @@ export default class ControllerProvider extends BaseProvider { this.profile = profile; }, methods: { - openSettings: this.openSettings.bind(this), - openPurchaseCredits: this.openPurchaseCredits.bind(this), - openExecute: this.openExecute.bind(this), + openSettings: () => this.openSettings.bind(this), + openPurchaseCredits: () => this.openPurchaseCredits.bind(this), + openExecute: () => this.openExecute.bind(this), }, rpcUrl: this.rpc.toString(), username, @@ -172,10 +172,15 @@ export default class ControllerProvider extends BaseProvider { console.error(new NotReadyToConnect().message); return null; } - this.iframes.profile?.close(); + if (this.iframes.profile?.sendBackward) { + this.iframes.profile?.sendBackward(); + } else { + this.iframes.profile?.close(); + } this.iframes.keychain.open(); const res = await this.keychain.openSettings(); this.iframes.keychain.close(); + this.iframes.profile?.sendForward?.(); if (res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED) { return false; } @@ -224,7 +229,7 @@ export default class ControllerProvider extends BaseProvider { this.keychain.openPurchaseCredits(); } - private openExecute(calls: any) { + async openExecute(calls: any) { if (!this.keychain || !this.iframes.keychain) { console.error(new NotReadyToConnect().message); return; @@ -233,9 +238,16 @@ export default class ControllerProvider extends BaseProvider { console.error("Profile is not ready"); return; } - this.iframes.profile.close(); + if (this.iframes.profile?.sendBackward) { + this.iframes.profile?.sendBackward(); + } else { + this.iframes.profile?.close(); + } this.iframes.keychain.open(); - this.keychain.execute(calls); + const res = await this.keychain.execute(calls, undefined, undefined, true); + this.iframes.keychain.close(); + this.iframes.profile?.sendForward?.(); + return !(res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED); } async delegateAccount() { diff --git a/packages/controller/src/iframe/base.ts b/packages/controller/src/iframe/base.ts index 699aa130f..d32d4e3ae 100644 --- a/packages/controller/src/iframe/base.ts +++ b/packages/controller/src/iframe/base.ts @@ -140,6 +140,16 @@ export class IFrame implements Modal { this.container.style.opacity = "0"; } + sendBackward() { + if (!this.container) return; + this.container.style.zIndex = "9999"; + } + + sendForward() { + if (!this.container) return; + this.container.style.zIndex = "10000"; + } + private resize() { if (!this.iframe) return; diff --git a/packages/controller/src/types.ts b/packages/controller/src/types.ts index c58b8705a..96530ef65 100644 --- a/packages/controller/src/types.ts +++ b/packages/controller/src/types.ts @@ -131,7 +131,7 @@ export interface Keychain { username(): string; fetchControllers(contractAddresses: string[]): Promise; openPurchaseCredits(): void; - openExecute(): void; + openExecute(calls: Call[]): Promise; } export interface Profile { navigate(path: string): void; diff --git a/packages/profile/.env.development b/packages/profile/.env.development index 6465bd4b8..361764ed7 100644 --- a/packages/profile/.env.development +++ b/packages/profile/.env.development @@ -1,5 +1,5 @@ -VITE_CARTRIDGE_API_URL="https://api.cartridge.gg" +VITE_CARTRIDGE_API_URL="http://localhost:8000" VITE_KEYCHAIN_URL="http://localhost:3001" -VITE_RPC_SEPOLIA="https://api.cartridge.gg/x/starknet/sepolia" +VITE_RPC_SEPOLIA="http://localhost:8001/x/starknet/sepolia" VITE_POSTHOG_KEY=phc_UWaJajNQ00PjHhveZ81SJ2zVtBicKrzewdZHGiyavQQ VITE_POSTHOG_HOST=https://profile.cartridge.gg/ingest diff --git a/packages/profile/src/components/context/connection.tsx b/packages/profile/src/components/context/connection.tsx index 86c9970d6..64341213d 100644 --- a/packages/profile/src/components/context/connection.tsx +++ b/packages/profile/src/components/context/connection.tsx @@ -31,7 +31,7 @@ type ParentMethods = { close: () => void; openSettings: () => void; openPurchaseCredits: () => void; - openExecute: (calls: Call[]) => void; + openExecute: (calls: Call[]) => Promise; }; const initialState: ConnectionContextType = { @@ -40,7 +40,7 @@ const initialState: ConnectionContextType = { close: () => {}, openSettings: () => {}, openPurchaseCredits: () => {}, - openExecute: () => {}, + openExecute: async () => {}, }, provider: new RpcProvider({ nodeUrl: import.meta.env.VITE_RPC_SEPOLIA }), chainId: "", diff --git a/packages/profile/src/components/inventory/collection/asset.tsx b/packages/profile/src/components/inventory/collection/asset.tsx index 08f203417..deae9b8cf 100644 --- a/packages/profile/src/components/inventory/collection/asset.tsx +++ b/packages/profile/src/components/inventory/collection/asset.tsx @@ -15,6 +15,8 @@ import { CardTitle, CopyText, ExternalIcon, + ScrollArea, + Separator, } from "@cartridge/ui-next"; import { addAddressPadding, constants } from "starknet"; import { @@ -134,111 +136,27 @@ export function Asset() { icon={asset.imageUrl ?? "/public/placeholder.svg"} /> - -
-
- + +
+ + + +
-
- - {asset.description && ( - - - Description - - - {asset.description} - - )} - - - - Properties - - - - {assets.map((a) => { - const trait = a.trait_type ?? a.trait; - return typeof a.value === "string" ? ( -
- {typeof trait === "string" ? ( -
- {trait} -
- ) : null} -
- {String(a.value)} -
-
- ) : null; - })} - {Array.from({ length: (assets.length - 1) % 3 }).map( - (_, i) => ( -
- ), - )} - - - - - - details - - -
Contract
- {isPublicChain(chainId) ? ( - -
- {formatAddress(col.address, { size: "sm" })} -
- - - ) : ( -
{formatAddress(col.address, { size: "sm" })}
- )} -
- - -
- Token ID -
-
- {asset.tokenId.startsWith("0x") - ? hexToNumber(asset.tokenId as Hex) - : asset.tokenId} -
-
- - -
- Token Standard -
-
{col.type}
-
-
+ + +
+ +
+ +
+
); } @@ -247,3 +165,136 @@ export function Asset() { ); } + +export const Image = ({ imageUrl }: { imageUrl: string | undefined }) => { + return ( +
+
+ +
+
+ ); +}; + +export const Description = ({ + description, +}: { + description: string | undefined; +}) => { + if (!description) return null; + return ( + + + + Description + + + + {description} + + ); +}; + +export const Properties = ({ + properties, +}: { + properties: Record[]; +}) => { + return ( + + + + Properties + + + + + {properties.map((property) => { + const trait = property.trait_type ?? property.trait; + return typeof property.value === "string" ? ( +
+ {typeof trait === "string" ? ( +
+ {trait} +
+ ) : null} +
+ {String(property.value)} +
+
+ ) : null; + })} + {Array.from({ length: (properties.length - 1) % 3 }).map((_, i) => ( +
+ ))} + + + ); +}; + +export const Details = ({ + chainId, + col, + asset, +}: { + chainId: constants.StarknetChainId; + col: Collection; + asset: Asset; +}) => { + return ( + + + + details + + + +
Contract Address
+ {isPublicChain(chainId) ? ( + +
+ {formatAddress(col.address, { size: "xs" })} +
+ + + ) : ( +
{formatAddress(col.address, { size: "sm" })}
+ )} +
+ + +
Token ID
+
+ {asset.tokenId.startsWith("0x") + ? hexToNumber(asset.tokenId as Hex) + : asset.tokenId} +
+
+ + +
Token Standard
+
{col.type}
+
+
+ ); +}; diff --git a/packages/profile/src/components/inventory/collection/collection.tsx b/packages/profile/src/components/inventory/collection/collection.tsx index 1cdf2e02b..5c8d38ffd 100644 --- a/packages/profile/src/components/inventory/collection/collection.tsx +++ b/packages/profile/src/components/inventory/collection/collection.tsx @@ -14,6 +14,7 @@ import { CardTitle, cn, CopyAddress, + ScrollArea, } from "@cartridge/ui-next"; import { LayoutContainer, @@ -114,94 +115,97 @@ export function Collection() { icon={col.imageUrl ?? "/public/placeholder.svg"} /> - - {/*
{ - setSearchParams({ - tokenIds: tokenIds.length - ? [] - : col.assets.map((a) => a.tokenId), - }); - }} - > - -
- {tokenIds.length - ? `${tokenIds.length} selected` - : "Select all"} -
-
*/} + + + {/*
{ + setSearchParams({ + tokenIds: tokenIds.length + ? [] + : col.assets.map((a) => a.tokenId), + }); + }} + > + +
+ {tokenIds.length + ? `${tokenIds.length} selected` + : "Select all"} +
+
*/} -
- {col.assets.map((a) => { - const isSelected = tokenIds.includes(a.tokenId); - return ( - - + {col.assets.map((a) => { + const isSelected = tokenIds.includes(a.tokenId); + return ( + - - - {a.name} - + + + + {a.name} + -
- -
-
- - - -
- - ); - })} -
+ setSearchParams({ + tokenIds: isSelected + ? tokenIds.filter( + (tokenId) => + tokenId !== a.tokenId, + ) + : [...tokenIds, a.tokenId], + }); + }} + > + {/* */} + +
+ + + + +
+ + ); + })} +
+ {!!tokenIds.length && ( diff --git a/packages/profile/src/components/inventory/index.tsx b/packages/profile/src/components/inventory/index.tsx index 8d7ed26db..77076dab3 100644 --- a/packages/profile/src/components/inventory/index.tsx +++ b/packages/profile/src/components/inventory/index.tsx @@ -1,14 +1,13 @@ export { Asset, Collection, SendCollection } from "./collection"; export { Token, SendToken } from "./token"; -import { CopyAddress } from "@cartridge/ui-next"; +import { CopyAddress, ScrollArea } from "@cartridge/ui-next"; import { LayoutContainer, LayoutContent, LayoutHeader, } from "@/components/layout"; import { Navigation } from "../navigation"; -// import { Collections } from "./collections"; import { Tokens } from "./token"; import { useAccount } from "@/hooks/account"; import { Outlet, useParams } from "react-router-dom"; @@ -34,8 +33,10 @@ export function Inventory() { /> - - {project && } + + + {project && } + ); diff --git a/packages/profile/src/components/inventory/token/send.tsx b/packages/profile/src/components/inventory/token/send.tsx index 8301cd94a..274a868ac 100644 --- a/packages/profile/src/components/inventory/token/send.tsx +++ b/packages/profile/src/components/inventory/token/send.tsx @@ -24,8 +24,8 @@ import { TokenPair } from "@cartridge/utils/api/cartridge"; import { zodResolver } from "@hookform/resolvers/zod"; import { useCallback, useMemo } from "react"; import { useForm } from "react-hook-form"; -import { Link, useParams } from "react-router-dom"; -import { constants } from "starknet"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import { Call, constants, uint256 } from "starknet"; import { z } from "zod"; export function SendToken() { @@ -33,6 +33,7 @@ export function SendToken() { const { address } = useAccount(); const { parent } = useConnection(); const t = useToken({ tokenAddress: tokenAddress! }); + const navigate = useNavigate(); const formSchema = useMemo(() => { // Avoid scientific notation in error message (e.g. `parseFloat(`1e-${decimals}`).toString() === "1e-18"`) @@ -80,25 +81,25 @@ export function SendToken() { }); const onSubmit = useCallback( - (values: z.infer) => { + async (values: z.infer) => { if (!t) return; - const amount = (values.amount * 10 ** t.meta.decimals).toString(); + const amount = uint256.bnToUint256( + BigInt(values.amount * 10 ** t.meta.decimals), + ); - parent.openExecute([ + const calls: Call[] = [ { - contractAddress: t?.meta.address, - entrypoint: "increaseAllowance", - calldata: [values.to, amount, "0x0"], - }, - { - contractAddress: t?.meta.address, + contractAddress: t.meta.address, entrypoint: "transfer", - calldata: [values.to, amount, "0x0"], + calldata: [values.to, amount], }, - ]); + ]; + await parent.openExecute(calls); + // Remove 3 sub routes from the path + navigate("../../.."); }, - [t, parent], + [t, parent, navigate], ); const amount = form.watch("amount"); @@ -112,7 +113,7 @@ export function SendToken() { ); if (!t) { - return; + return null; } return ( diff --git a/packages/profile/src/components/inventory/token/token.tsx b/packages/profile/src/components/inventory/token/token.tsx index 8fdaf5013..81c411044 100644 --- a/packages/profile/src/components/inventory/token/token.tsx +++ b/packages/profile/src/components/inventory/token/token.tsx @@ -93,7 +93,6 @@ function Credits() { function ERC20() { const { address } = useParams<{ address: string }>(); - // const [searchParams, setSearchParams] = useSearchParams(); const { chainId } = useConnection(); const t = useToken({ tokenAddress: address! }); @@ -168,13 +167,13 @@ function ERC20() {
- {/* {isIframe() && ( + {isIframe() && ( - )} */} + )} ); } diff --git a/packages/profile/src/components/inventory/token/tokens.tsx b/packages/profile/src/components/inventory/token/tokens.tsx index 62d465120..9fcecb71e 100644 --- a/packages/profile/src/components/inventory/token/tokens.tsx +++ b/packages/profile/src/components/inventory/token/tokens.tsx @@ -1,9 +1,16 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@cartridge/ui-next"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + cn, +} from "@cartridge/ui-next"; import { Link } from "react-router-dom"; import { Balance, ERC20Metadata, useCountervalue } from "@cartridge/utils"; import { formatEther } from "viem"; import { useTokens } from "@/hooks/token"; import { TokenPair } from "@cartridge/utils/api/cartridge"; +import { useState } from "react"; export function Tokens() { // const { isVisible } = useConnection(); @@ -53,14 +60,22 @@ function TokenCardContent({ }: { token: { balance: Balance; meta: ERC20Metadata }; }) { + const [hover, setHover] = useState(false); const { countervalue } = useCountervalue({ balance: formatEther(token.balance.value || 0n), pair: `${token.meta.symbol}_USDC` as TokenPair, }); return ( - -
+ setHover(true)} + onMouseLeave={() => setHover(false)} + > +
import.meta.env.VITE_MOCKED_ACCOUNT_ADDRESS ?? data?.account?.controllers.edges?.[0]?.node?.address ?? "", + [data], + ); + + return { + username, + address, }; } diff --git a/packages/ui-next/src/preset.ts b/packages/ui-next/src/preset.ts index ae03f01e4..415af5103 100644 --- a/packages/ui-next/src/preset.ts +++ b/packages/ui-next/src/preset.ts @@ -56,6 +56,9 @@ export const cartridgeTWPreset: Partial = { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, + spacer: { + DEFAULT: "hsl(var(--spacer))", + }, }, extend: { fontFamily: { diff --git a/packages/ui-next/src/themes/dark.css b/packages/ui-next/src/themes/dark.css index 3f314b73f..d064a347b 100644 --- a/packages/ui-next/src/themes/dark.css +++ b/packages/ui-next/src/themes/dark.css @@ -33,5 +33,7 @@ --border: 0 0% 50%; --input: 120 7.5% 15.69%; /* bg.secondary */ --ring: 0 0% 100%; /* text.primary */ + + --spacer: 132 14% 7%; /* solid-fills/spacer */ } } diff --git a/packages/ui-next/src/themes/default.css b/packages/ui-next/src/themes/default.css index d8d9145ed..88a4878d2 100644 --- a/packages/ui-next/src/themes/default.css +++ b/packages/ui-next/src/themes/default.css @@ -35,5 +35,7 @@ --ring: 0 0% 0%; --radius: 0.5rem; + + --spacer: 0 0% 0%; /* solid-fills/spacer */ } }