diff --git a/examples/starknet-react-next/src/components/StarknetProvider.tsx b/examples/starknet-react-next/src/components/StarknetProvider.tsx index 9d506c99b..7e3a41562 100644 --- a/examples/starknet-react-next/src/components/StarknetProvider.tsx +++ b/examples/starknet-react-next/src/components/StarknetProvider.tsx @@ -52,7 +52,7 @@ const connectors = [ ], { url, - rpc: process.env.NEXT_PUBLIC_RPC_SEPOLIA + rpc: process.env.NEXT_PUBLIC_RPC_SEPOLIA, // theme: "rollyourown", // colorMode: "light" }, diff --git a/packages/keychain/src/components/Container/Header/index.tsx b/packages/keychain/src/components/Container/Header/index.tsx deleted file mode 100644 index 63fd9f0e6..000000000 --- a/packages/keychain/src/components/Container/Header/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useMemo } from "react"; -import { - Flex, - Spacer, - HStack, - Container as ChakraContainer, - StyleProps, - IconButton, - VStack, - Image, - Center, - useColorMode, -} from "@chakra-ui/react"; -import { ArrowLeftIcon, CartridgeColorIcon } from "@cartridge/ui"; -// import { NetworkButton } from "./NetworkButton"; -// import { EthBalance } from "./EthBalance"; -// import { AccountMenu } from "./AccountMenu"; -import { useController } from "hooks/controller"; -import { useControllerTheme } from "hooks/theme"; - -export type HeaderProps = { - chainId?: string; - onLogout?: () => void; - onBack?: () => void; - hideAccount?: boolean; -}; - -export function Header({ - // chainId, - // onLogout, - onBack, - hideAccount, -}: HeaderProps) { - const { controller } = useController(); - const address = useMemo(() => controller?.address, [controller]); - const theme = useControllerTheme(); - const { colorMode } = useColorMode(); - - const cover = useMemo( - () => - typeof theme.cover === "string" ? theme.cover : theme.cover[colorMode], - [theme, colorMode], - ); - - if (!address || hideAccount) { - return ( - - -
- - Controller Icon - -
-
-
- ); - } - - return ( - - - {onBack ? ( - } - onClick={onBack} - /> - ) : theme.id === "cartridge" ? ( - - ) : ( - Controller Icon - )} - - - - {/* */} - {/* */} - - {/* {chainId && } */} - - - ); -} - -function Container({ - h, - children, - ...rest -}: { - children: React.ReactNode; -} & StyleProps) { - return ( - - - {children} - - - ); -} - -export const BANNER_HEIGHT = 150; -export const ICON_IMAGE_SIZE = 64; -export const ICON_SIZE = 80; -export const ICON_OFFSET = 32; diff --git a/packages/keychain/src/components/DeploymentRequired.tsx b/packages/keychain/src/components/DeploymentRequired.tsx index f976cb39c..8e2c9ceb7 100644 --- a/packages/keychain/src/components/DeploymentRequired.tsx +++ b/packages/keychain/src/components/DeploymentRequired.tsx @@ -1,26 +1,22 @@ import { constants } from "starknet"; -import Controller from "utils/controller"; -import { Container } from "./Container"; +import { Container, Banner, Footer, Content } from "components/layout"; import { useEffect, useState } from "react"; import { Status } from "utils/account"; import { Loading } from "./Loading"; import { Button, Link, Text } from "@chakra-ui/react"; import { ExternalIcon } from "@cartridge/ui"; -import { PortalBanner } from "./PortalBanner"; -import { PortalFooter } from "./PortalFooter"; +import { useController } from "hooks/controller"; + export function DeploymentRequired({ - chainId, - controller, onClose, onLogout, children, }: { - chainId: string; - controller: Controller; onClose: () => void; onLogout: () => void; children: React.ReactNode; }) { + const { controller } = useController() const account = controller.account; const [status, setStatus] = useState(account.status); const [deployHash, setDeployHash] = useState(); @@ -65,40 +61,41 @@ export function DeploymentRequired({ if (status !== Status.DEPLOYED) { return ( - - + - {status === Status.DEPLOYING && ( - + {status === Status.DEPLOYING && ( + - - - )} + }starkscan.co/tx/${deployHash}`} + isExternal + > + + + )} - {error && ( - <> - - We encounter an account deployment error: {error.message} - - Please come by discord and report this issue. - - )} + {error && ( + <> + + We encounter an account deployment error: {error.message} + + Please come by discord and report this issue. + + )} + - +
- +
); } diff --git a/packages/keychain/src/components/Execute/Call.tsx b/packages/keychain/src/components/Execute/Call.tsx index 8c89cacc9..68cf35e53 100644 --- a/packages/keychain/src/components/Execute/Call.tsx +++ b/packages/keychain/src/components/Execute/Call.tsx @@ -3,11 +3,9 @@ import { CodeUtilIcon } from "@cartridge/ui"; import { HStack, Spacer, SystemProps, Text } from "@chakra-ui/react"; export function Call({ - chainId, policy, ...rest }: { - chainId: string; policy: Policy; } & SystemProps) { return ( diff --git a/packages/keychain/src/components/Execute/Fees.tsx b/packages/keychain/src/components/Execute/Fees.tsx index e59e9b165..7b3f83d41 100644 --- a/packages/keychain/src/components/Execute/Fees.tsx +++ b/packages/keychain/src/components/Execute/Fees.tsx @@ -11,6 +11,7 @@ import { import { constants } from "starknet"; import { formatUnits } from "viem"; import { Error } from "components/Error"; +import { useChainId } from "hooks/connection"; async function fetchEthPrice() { const res = await fetch(process.env.NEXT_PUBLIC_API_URL, { @@ -25,17 +26,16 @@ async function fetchEthPrice() { export function Fees({ error, - chainId, fees, balance, approved, }: { error: Error; - chainId: string; fees?: { base: bigint; max: bigint }; balance: string; approved?: string; }) { + const chainId = useChainId(); const [formattedFee, setFormattedFee] = useState<{ base: string; max: string; @@ -66,13 +66,13 @@ export function Fees({ setFormattedFee( fees.max > 10000000000000n ? { - base: `~${parseFloat(formatUnits(fees.base, 18)).toFixed(5)} eth`, - max: `~${parseFloat(formatUnits(fees.max, 18)).toFixed(5)} eth`, - } + base: `~${parseFloat(formatUnits(fees.base, 18)).toFixed(5)} eth`, + max: `~${parseFloat(formatUnits(fees.max, 18)).toFixed(5)} eth`, + } : { - base: "<0.00001", - max: "<0.00001", - }, + base: "<0.00001", + max: "<0.00001", + }, ); } compute(); diff --git a/packages/keychain/src/components/Execute/LowEth.tsx b/packages/keychain/src/components/Execute/InsufficientFunds.tsx similarity index 89% rename from packages/keychain/src/components/Execute/LowEth.tsx rename to packages/keychain/src/components/Execute/InsufficientFunds.tsx index 172aa9121..f47935eb5 100644 --- a/packages/keychain/src/components/Execute/LowEth.tsx +++ b/packages/keychain/src/components/Execute/InsufficientFunds.tsx @@ -1,29 +1,26 @@ import { CopyIcon, EthereumIcon, StarknetIcon } from "@cartridge/ui"; import { HStack, Spacer, Text, VStack } from "@chakra-ui/react"; -import { Container } from "components/Container"; -import { PortalBanner } from "components/PortalBanner"; +import { Container, Content, Banner } from "components/layout"; import { useState } from "react"; import { BigNumberish } from "starknet"; import { formatAddress } from "utils/contracts"; -const NewLowEth = ({ - chainId, +export function InsufficientFunds({ address, balance, }: { - chainId: string; address: BigNumberish; balance: BigNumberish; -}) => { +}) { const [copied, setCopied] = useState(false); return ( - - + - + BALANCE @@ -88,9 +85,7 @@ const NewLowEth = ({ )} - + ); }; - -export default NewLowEth; diff --git a/packages/keychain/src/components/Execute/index.tsx b/packages/keychain/src/components/Execute/index.tsx index 9b15184ec..e75f7720a 100644 --- a/packages/keychain/src/components/Execute/index.tsx +++ b/packages/keychain/src/components/Execute/index.tsx @@ -1,54 +1,51 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Text, VStack, Button } from "@chakra-ui/react"; - -import Controller from "utils/controller"; import { Call as StarknetCall, InvocationsDetails } from "starknet"; import { Fees } from "./Fees"; import { formatEther } from "viem"; import { ExecuteReply, ResponseCodes } from "@cartridge/controller"; -import { Container } from "../Container"; +import { + Container, + Content, + Banner, + FOOTER_MIN_HEIGHT, + Footer, +} from "components/layout"; import { Status } from "utils/account"; -import { PortalBanner } from "../PortalBanner"; import { TransactionDuoIcon } from "@cartridge/ui"; import { Call } from "./Call"; -import { - PORTAL_FOOTER_MIN_HEIGHT, - PortalFooter, -} from "components/PortalFooter"; -import LowEth from "./LowEth"; +import { InsufficientFunds } from "./InsufficientFunds"; +import { useChainId, useOrigin } from "hooks/connection"; +import { useController } from "hooks/controller"; export const CONTRACT_ETH = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"; export function Execute({ - // origin, - chainId, - controller, transactions, transactionsDetail, - // abis, onExecute, onCancel, onLogout, }: { - // origin: string; - chainId: string; - controller: Controller; transactions: StarknetCall | StarknetCall[]; transactionsDetail?: InvocationsDetails; - // abis?: Abi[]; onExecute: (res: ExecuteReply) => void; onCancel: () => void; onLogout: () => void; }) { + const chainId = useChainId(); + const { controller } = useController(); + const origin = useOrigin(); + const [fees, setFees] = useState<{ base: bigint; max: bigint; }>(); const [error, setError] = useState(); const [isLoading, setLoading] = useState(false); - const [ethBalance, setEthBalance] = useState(); - const [lowEth, setLowEth] = useState(false); + const [ethBalance, setEthBalance] = useState(0n); + const [isInsufficient, setIsInsufficient] = useState(false); const account = controller.account; const calls = useMemo(() => { @@ -122,7 +119,7 @@ export function Execute({ } if (ethBalance < fees.max) { - setLowEth(true); + setIsInsufficient(true); } }, [ethBalance, fees]); @@ -137,10 +134,9 @@ export function Execute({ }); }, [account, calls, fees, onExecute]); - if (lowEth) { + if (isInsufficient) { return ( - @@ -148,10 +144,10 @@ export function Execute({ } return ( - - + + - + @@ -163,7 +159,6 @@ export function Execute({ {calls.map((call, i) => ( - - - - - - - - - - + + + +
+ + + +
); } diff --git a/packages/keychain/src/components/PortalBanner.tsx b/packages/keychain/src/components/PortalBanner.tsx deleted file mode 100644 index 2c328faa1..000000000 --- a/packages/keychain/src/components/PortalBanner.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { VStack, Circle, Text, IconProps } from "@chakra-ui/react"; - -export function PortalBanner({ - Icon, - icon, - title, - description, -}: { - Icon?: React.ComponentType; - icon?: React.ReactElement; - title: string; - description?: string | React.ReactElement; -}) { - return ( - - {!!Icon && ( - - - - )} - - {!!icon && ( - - {icon} - - )} - - - {title} - - - {description && ( - - {description} - - )} - - ); -} diff --git a/packages/keychain/src/components/PortalFooter/index.tsx b/packages/keychain/src/components/PortalFooter/index.tsx deleted file mode 100644 index 9b30df11a..000000000 --- a/packages/keychain/src/components/PortalFooter/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { - HStack, - IconButton, - Spacer, - VStack, - useDisclosure, -} from "@chakra-ui/react"; -import { TransactionSummary } from "./TransactionSummary"; -import { WedgeUpIcon } from "@cartridge/ui"; -import { Policy } from "@cartridge/controller"; -import { SessionDetails } from "./SessionDetails"; -import React, { useMemo } from "react"; -import { FOOTER_HEIGHT } from "components"; -import { - BANNER_HEIGHT, - ICON_OFFSET, - ICON_SIZE, -} from "components/Container/Header"; -import { motion } from "framer-motion"; - -export function PortalFooter({ - children, - origin, - policies, - isSignup, - isSlot, - showTerm = false, -}: React.PropsWithChildren & { - origin?: string; - policies?: Policy[]; - isSignup?: boolean; - isSlot?: boolean; - showTerm?: boolean; -}) { - const { isOpen, onToggle } = useDisclosure(); - const isExpandable = useMemo(() => !!origin, [origin]); - const hostname = useMemo( - () => (origin ? new URL(origin).hostname : undefined), - [origin], - ); - - const height = useMemo( - () => - isOpen - ? `${ - window.innerHeight - - BANNER_HEIGHT - - FOOTER_HEIGHT + - ICON_SIZE / 2 - - ICON_OFFSET - }px` - : "auto", - [isOpen], - ); - - return ( - - - - - - - - {isExpandable && ( - - } - size="sm" - bg="solid.primary" - zIndex="999999" - onClick={onToggle} - /> - )} - - - {isOpen && policies && ( - - )} - - {/* TODO: starter pack - starterData && remaining > 0 && ( - <> - - {starterData.game.starterPack.starterPackTokens.map( - (data, key) => ( - - ), - )} - - - - - - - - Claim Starterpack - - - - ) */} - - - - - - {children} - - - ); -} - -export const PORTAL_FOOTER_MIN_HEIGHT = 252; diff --git a/packages/keychain/src/components/Quests/Card.tsx b/packages/keychain/src/components/Quests/Card.tsx deleted file mode 100644 index 5cedf076f..000000000 --- a/packages/keychain/src/components/Quests/Card.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { CircleCheckIcon, SparklesIcon } from "@cartridge/ui"; -import { HStack, Spacer, Text } from "@chakra-ui/react"; -import { QuestState } from "./types"; - -export function QuestCard({ - name, - state, -}: { - name: string; - state: QuestState; -}) { - let bgColor = "solid.primary"; - let color = "white"; - let check = false; - let border = undefined; - - switch (state) { - case QuestState.Claimable: - bgColor = "solid.secondary"; - color = "green.400"; - check = true; - break; - case QuestState.Complete: - bgColor = "transparent"; - color = "text.secondary"; - check = true; - border = { - border: "1px solid", - borderColor: "solid.primary", - }; - break; - default: - break; - } - - return ( - - {check && } - {name} - - {state !== QuestState.Complete && ( - - )} - {state === QuestState.Complete && ( - - Completed - - )} - - ); -} diff --git a/packages/keychain/src/components/Quests/Details.tsx b/packages/keychain/src/components/Quests/Details.tsx deleted file mode 100644 index 29080419c..000000000 --- a/packages/keychain/src/components/Quests/Details.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { Box, Button, Flex, HStack, Text, VStack } from "@chakra-ui/react"; -import { QuestState } from "./types"; -import { useEffect, useState } from "react"; -import { useClaimQuestRewardsMutation } from "generated/graphql"; -import { - CheckIcon, - CircleCheckIcon, - CircleNoCheckIcon, - QuestsDuoIcon, - SparklesIcon, -} from "@cartridge/ui"; -import { PortalBanner } from "components/PortalBanner"; - -export function QuestDetails({ - questsWithProgress, - selectedId, - address, - onClaim, -}: { - questsWithProgress: Array<{ quest: any; progress: any }>; - selectedId: string; - address: string; - onClaim: () => void; -}) { - const [quest, setQuest] = useState(); - const [questState, setQuestState] = useState(); - const [questProgress, setQuestProgress] = useState(); - const { mutateAsync } = useClaimQuestRewardsMutation(); - - useEffect(() => { - const q = questsWithProgress.find((qwp) => qwp.quest.id === selectedId); - setQuest(q.quest); - setQuestProgress(q.progress); - setQuestState( - q.progress?.claimed - ? QuestState.Complete - : q.progress?.completed - ? QuestState.Claimable - : QuestState.Incomplete, - ); - }, [questsWithProgress, selectedId]); - - if (quest === undefined) { - return <>; - } - - return ( - - - - - - - - - {typeof questState !== "undefined" || - (questState !== QuestState.Incomplete && ( - - ))} - {typeof questState === "undefined" || - questState === QuestState.Incomplete - ? "incomplete" - : "completed"} - - - - - - Requirements - {quest.questEvents?.length && - quest.questEvents?.map((evt) => { - const eventProgress = questProgress?.completion?.find( - (c) => c.questEvent === evt.id, - ); - return ( - - {eventProgress?.completed ? ( - - ) : ( - - )} - - {evt.description} - - - ); - })} - - - Rewards - {quest.points && ( - } - amount={quest.points} - name="XP" - /> - )} - - - - - - - - ); -} - -const Tag = ({ children }: { children: React.ReactNode }) => { - return ( - - {children} - - ); -}; - -const Reward = ({ - icon, - name, - amount, -}: { - icon: React.ReactNode; - name: string; - amount: string; -}) => { - return ( - - - {icon} - - - {amount} {name} - - - ); -}; diff --git a/packages/keychain/src/components/Quests/Overview.tsx b/packages/keychain/src/components/Quests/Overview.tsx deleted file mode 100644 index 54e1f3f01..000000000 --- a/packages/keychain/src/components/Quests/Overview.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; -import { QuestState } from "./types"; -import { QuestCard } from "./Card"; -import { PortalBanner } from "components/PortalBanner"; -import { QuestsDuoIcon } from "@cartridge/ui"; - -export function QuestOverview({ - questsWithProgression, - onSelect, -}: { - questsWithProgression: Array<{ quest: any; progress: any }>; - onSelect: (id: string) => void; -}) { - return ( - <> - - - - <> - - {questsWithProgression?.map((qwp) => { - if (!qwp.progress?.claimed) { - return ( - onSelect(qwp.quest.id)} - > - - - ); - } - })} - - - - {questsWithProgression?.map((qwp) => { - if (qwp.progress?.claimed) { - return ( - onSelect(qwp.quest.id)} - > - - - ); - } - })} - - - - ); -} - -const Label = ({ children }: { children: React.ReactNode }) => ( - - - {children} - - -); diff --git a/packages/keychain/src/components/Quests/index.tsx b/packages/keychain/src/components/Quests/index.tsx deleted file mode 100644 index 8a5e9f814..000000000 --- a/packages/keychain/src/components/Quests/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Container } from "../Container"; -import { useEffect, useState } from "react"; -import { useAccountInfoQuery, useAccountQuestsQuery } from "generated/graphql"; -import { constants } from "starknet"; -import { QuestOverview } from "./Overview"; -import { QuestDetails } from "./Details"; -import { Flex, Text, useToast } from "@chakra-ui/react"; - -export function Quests({ - gameId, - address, - chainId, - onLogout, -}: { - gameId: string; - address: string; - chainId: constants.StarknetChainId; - onLogout: () => void; -}) { - const toast = useToast(); - const [selectedQuestId, setSelectedQuestId] = useState(); - const [questsWithProgression, setQuestsWithProgression] = - useState>(); - const { data: accountData } = useAccountInfoQuery({ address }); - const accountId = accountData?.accounts?.edges[0]?.node.id; - const { data: questData, refetch: refetchQuests } = useAccountQuestsQuery( - { accountId, gameId }, - { - enabled: !!accountId, - }, - ); - const questProgression = questData?.account?.questProgression?.edges; - const quests = questData?.quests?.edges; - - useEffect(() => { - const qwp: Array<{ quest: any; progress: any }> = []; - quests?.forEach(({ node: q }) => { - const progress = questProgression?.find( - ({ node: qp }) => qp.questID === q.id, - ); - qwp.push({ - quest: q, - progress: progress?.node ?? null, - }); - }); - setQuestsWithProgression(qwp); - }, [quests, questProgression]); - - return ( - setSelectedQuestId(null) : undefined} - onLogout={onLogout} - > - {!selectedQuestId ? ( - setSelectedQuestId(id)} - /> - ) : ( - { - const res = await refetchQuests(); - const quest = res.data.quests?.edges?.find((q) => q.node?.id); - const progress = res.data.account?.questProgression.edges?.find( - (qp) => qp.node?.questID === quest?.node?.id, - ); - if (progress.node?.claimed) { - toast({ - position: "top-right", - title: "Quest Complete", - render: () => ( - - Quest Complete - - ), - duration: 2000, - }); - } - }} - /> - )} - - ); -} diff --git a/packages/keychain/src/components/Quests/types.ts b/packages/keychain/src/components/Quests/types.ts deleted file mode 100644 index f088fac47..000000000 --- a/packages/keychain/src/components/Quests/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum QuestState { - Incomplete, - Claimable, - Complete, -} diff --git a/packages/keychain/src/components/Redeploy.tsx b/packages/keychain/src/components/Redeploy.tsx index 560db09a8..04464a3cf 100644 --- a/packages/keychain/src/components/Redeploy.tsx +++ b/packages/keychain/src/components/Redeploy.tsx @@ -1,22 +1,22 @@ -import { constants, addAddressPadding } from "starknet"; -import { Container } from "./Container"; +import { addAddressPadding } from "starknet"; +import { Container, Banner } from "components/layout"; import Controller from "utils/controller"; import { useEffect } from "react"; import { client } from "utils/graphql"; import { DeployAccountDocument, AccountInfoDocument } from "generated/graphql"; import { Status } from "utils/account"; import { SparklesDuoIcon } from "@cartridge/ui"; -import { PortalBanner } from "./PortalBanner"; +import { useChainId } from "hooks/connection"; export function Redeploy({ - chainId, controller, onLogout, }: { - chainId: constants.StarknetChainId; controller: Controller; onLogout: () => void; }) { + const chainId = useChainId(); + useEffect(() => { const deploy = async () => { const result = await client.request(AccountInfoDocument, { @@ -39,8 +39,8 @@ export function Redeploy({ }, [chainId, controller]); return ( - - + void; onCancel: () => void; onLogout: () => void; }) { + const { controller } = useController(); const [messageData, setMessageData] = useState(); useEffect(() => { @@ -64,53 +59,55 @@ export function SignMessage({ }, [controller, onSign, typedData]); return ( - - + - - {(() => { - if (!messageData) return <>; - const ptName = messageData.primaryType; - const pt = messageData.types[ptName]; - const values = (typeName: string) => { - const v = messageData.message[typeName]; - if (typeof v === "object") { - return Object.entries(v).map(([key, value]) => { - return ( - - - {key}: - {" "} - {value as string} - - ); - }); - } else { - return {v as string}; - } - }; + + + {(() => { + if (!messageData) return <>; + const ptName = messageData.primaryType; + const pt = messageData.types[ptName]; + const values = (typeName: string) => { + const v = messageData.message[typeName]; + if (typeof v === "object") { + return Object.entries(v).map(([key, value]) => { + return ( + + + {key}: + {" "} + {value as string} + + ); + }); + } else { + return {v as string}; + } + }; - return pt.map((typ) => { - return ( - - {values(typ.name)} - - ); - }); - })()} - + return pt.map((typ) => { + return ( + + {values(typ.name)} + + ); + }); + })()} + + - +
- +
); } diff --git a/packages/keychain/src/components/bridge/BridgeEth.tsx b/packages/keychain/src/components/bridge/BridgeEth.tsx index d7b6972c5..f7876fccb 100644 --- a/packages/keychain/src/components/bridge/BridgeEth.tsx +++ b/packages/keychain/src/components/bridge/BridgeEth.tsx @@ -26,6 +26,7 @@ import { TransferButton } from "./TransferButton"; import { TxnTracker } from "./Transactions"; import { Label } from "./Label"; import Controller from "utils/controller"; +import { Banner } from "components/layout"; import { Error } from "components/Error"; import { CheckIcon, @@ -34,7 +35,6 @@ import { MetaMaskIcon, WedgeDownIcon, } from "@cartridge/ui"; -import { PortalBanner } from "components/PortalBanner"; export function BridgeEth({ chainId, @@ -116,7 +116,7 @@ export function BridgeEth({ return ( - + @@ -130,8 +130,8 @@ export function BridgeEth({ text={ !!ethAddress ? ethAddress.substring(0, 3) + - "..." + - ethAddress.substring(ethAddress.length - 4) + "..." + + ethAddress.substring(ethAddress.length - 4) : "Metamask" } pointerEvents={!!ethAddress ? "none" : "auto"} diff --git a/packages/keychain/src/components/bridge/Transactions.tsx b/packages/keychain/src/components/bridge/Transactions.tsx index d7749f5f8..a230d3a43 100644 --- a/packages/keychain/src/components/bridge/Transactions.tsx +++ b/packages/keychain/src/components/bridge/Transactions.tsx @@ -6,7 +6,7 @@ import { constants } from "starknet"; import { mainnet, useWaitForTransaction } from "wagmi"; import { sepolia } from "wagmi/chains"; import { CheckIcon, ExternalIcon, TransferDuoIcon } from "@cartridge/ui"; -import { PortalBanner } from "components/PortalBanner"; +import { Banner } from "components/layout"; enum CardState { PENDING = "PENDING", @@ -30,7 +30,7 @@ export function TxnTracker({ return ( - + {txn.state === CardState.PENDING && ( <> diff --git a/packages/keychain/src/components/connect/Authenticate.tsx b/packages/keychain/src/components/connect/Authenticate.tsx deleted file mode 100644 index 9815d3c67..000000000 --- a/packages/keychain/src/components/connect/Authenticate.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useEffect, useState, useCallback } from "react"; -import { Button } from "@chakra-ui/react"; -import { Unsupported } from "./Unsupported"; -import { doSignup } from "hooks/account"; -import { Container } from "../Container"; -import { PortalBanner } from "components/PortalBanner"; -import { PortalFooter } from "components/PortalFooter"; - -type AuthAction = "signup" | "login"; - -export function Authenticate({ - name, - action, - onSuccess, -}: { - name: string; - action: AuthAction; - onSuccess: () => void; -}) { - const [isLoading, setIsLoading] = useState(false); - const [unsupportedMessage, setUnsupportedMessage] = useState(); - - const onAuth = useCallback(async () => { - setIsLoading(true); - - try { - switch (action) { - case "signup": - await doSignup(decodeURIComponent(name)); - break; - case "login": - break; - default: - throw new Error(`Unsupported action ${action}`); - } - - onSuccess(); - } catch (e) { - console.error(e); - setIsLoading(false); - throw e; - } - }, [onSuccess, action, name]); - - useEffect(() => { - const userAgent = window.navigator.userAgent; - if (/iPad|iPhone|iPod/.test(userAgent)) { - const iosVersion = /OS (\d+)_?(?:\d+_?){0,2}\s/.exec(userAgent); - if (iosVersion && Number(iosVersion[1]) < 16) { - setUnsupportedMessage( - `iOS ${iosVersion[1]} does not support passkeys. Upgrade to iOS 16 to continue`, - ); - } - } - }, []); - - if (unsupportedMessage) { - return ; - } - - const title = - action === "signup" ? "Authenticate Yourself" : "Hello from Cartridge!"; - const description = - action === "signup" ? ( - <> - You will now be asked to authenticate yourself. -
- Note: this experience varies from browser to browser. - - ) : ( - <>Please click continue. - ); - return ( - <> - - - - - - - - - ); -} diff --git a/packages/keychain/src/components/connect/Authenticate/Unsupported.tsx b/packages/keychain/src/components/connect/Authenticate/Unsupported.tsx new file mode 100644 index 000000000..3f1a3d539 --- /dev/null +++ b/packages/keychain/src/components/connect/Authenticate/Unsupported.tsx @@ -0,0 +1,33 @@ +import { AlertIcon } from "@cartridge/ui"; +import { Container, Banner } from "components/layout"; +import { useEffect, useState } from "react"; + +export function Unsupported({ message }: { message: string }) { + return ( + + + + ); +} + +export function useIsSupported() { + const [message, setMessage] = useState(); + + useEffect(() => { + const userAgent = window.navigator.userAgent; + if (/iPad|iPhone|iPod/.test(userAgent)) { + const iosVersion = /OS (\d+)_?(?:\d+_?){0,2}\s/.exec(userAgent); + if (iosVersion && Number(iosVersion[1]) < 16) { + setMessage( + `iOS ${iosVersion[1]} does not support passkeys. Upgrade to iOS 16 to continue`, + ); + } + } + }, []); + + return { isSupported: !message, message }; +} diff --git a/packages/keychain/src/components/connect/Authenticate/index.tsx b/packages/keychain/src/components/connect/Authenticate/index.tsx new file mode 100644 index 000000000..9cbde1228 --- /dev/null +++ b/packages/keychain/src/components/connect/Authenticate/index.tsx @@ -0,0 +1,72 @@ +import React, { useState, useCallback } from "react"; +import { Button } from "@chakra-ui/react"; +import { Unsupported, useIsSupported } from "./Unsupported"; +import { doSignup } from "hooks/account"; +import { Container, Banner, Footer } from "components/layout"; + +export type AuthAction = "signup" | "login"; + +export function Authenticate({ + name, + action, + onSuccess, +}: { + name: string; + action: AuthAction; + onSuccess: () => void; +}) { + const [isLoading, setIsLoading] = useState(false); + const { isSupported, message } = useIsSupported(); + + const onAuth = useCallback(async () => { + setIsLoading(true); + + try { + switch (action) { + case "signup": + await doSignup(decodeURIComponent(name)); + break; + case "login": + break; + default: + throw new Error(`Unsupported action ${action}`); + } + + onSuccess(); + } catch (e) { + console.error(e); + throw e; + } finally { + setIsLoading(false); + } + }, [onSuccess, action, name]); + + if (!isSupported) { + return ; + } + + const title = + action === "signup" ? "Authenticate Yourself" : "Hello from Cartridge!"; + const description = + action === "signup" ? ( + <> + You will now be asked to authenticate yourself. +
+ Note: this experience varies from browser to browser. + + ) : ( + <>Please click continue. + ); + + return ( + + + +
+ +
+
+ ); +} diff --git a/packages/keychain/src/components/connect/BannerImage.tsx b/packages/keychain/src/components/connect/BannerImage.tsx deleted file mode 100644 index 944f0afcc..000000000 --- a/packages/keychain/src/components/connect/BannerImage.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Box, StyleProps } from "@chakra-ui/react"; -import Image from "next/image"; - -export function BannerImage({ - imgSrc, - ...rest -}: { - imgSrc?: string; -} & StyleProps) { - return ( - - {imgSrc && ( - banner - )} - - - ); -} diff --git a/packages/keychain/src/components/connect/CreateSession.tsx b/packages/keychain/src/components/connect/CreateSession.tsx index 501e48b35..b6a692bef 100644 --- a/packages/keychain/src/components/connect/CreateSession.tsx +++ b/packages/keychain/src/components/connect/CreateSession.tsx @@ -1,4 +1,4 @@ -import { Container, PortalBanner, PortalFooter } from "components"; +import { Container, Banner, Footer } from "components/layout"; import { BigNumberish } from "starknet"; import { Policy } from "@cartridge/controller"; import { PlugNewDuoIcon } from "@cartridge/ui"; @@ -7,14 +7,12 @@ import { useState } from "react"; import { useController } from "hooks/controller"; export function CreateSession({ - chainId, policies, origin, onConnect, onCancel, onLogout, }: { - chainId: string; policies: Policy[]; origin: string; onConnect: (policies: Policy[]) => void; @@ -26,37 +24,35 @@ export function CreateSession({ const [expiresAt] = useState(3000000000n); const [maxFees] = useState(); return ( - - + - - <> - +
+ - - - + +
); } diff --git a/packages/keychain/src/components/connect/Login.tsx b/packages/keychain/src/components/connect/Login.tsx index a3fa3f228..c2ee7c6ea 100644 --- a/packages/keychain/src/components/connect/Login.tsx +++ b/packages/keychain/src/components/connect/Login.tsx @@ -1,12 +1,13 @@ import { Field } from "@cartridge/ui"; -import { VStack, Button } from "@chakra-ui/react"; -import { Container } from "../Container"; -import { Form as FormikForm, Field as FormikField, Formik } from "formik"; +import { Button } from "@chakra-ui/react"; import { - PORTAL_FOOTER_MIN_HEIGHT, - PortalBanner, - PortalFooter, -} from "components"; + Container, + FOOTER_MIN_HEIGHT, + Banner, + Footer, + Content, +} from "components/layout"; +import { Form as FormikForm, Field as FormikField, Formik } from "formik"; import { useCallback, useState } from "react"; import Controller from "utils/controller"; import { FormValues, LoginMode, LoginProps } from "./types"; @@ -104,7 +105,7 @@ export function Login({ ); return ( - + {(props) => ( - - + - + - +
)}
diff --git a/packages/keychain/src/components/Logout.tsx b/packages/keychain/src/components/connect/Logout.tsx similarity index 71% rename from packages/keychain/src/components/Logout.tsx rename to packages/keychain/src/components/connect/Logout.tsx index 268186b17..afcef879f 100644 --- a/packages/keychain/src/components/Logout.tsx +++ b/packages/keychain/src/components/connect/Logout.tsx @@ -1,8 +1,6 @@ import { Button } from "@chakra-ui/react"; -import { Container } from "./Container"; -import { PortalBanner } from "./PortalBanner"; +import { Container, Banner, Footer } from "components/layout"; import { LogoutDuoIcon } from "@cartridge/ui"; -import { PortalFooter } from "./PortalFooter"; export function Logout({ onConfirm, @@ -13,18 +11,18 @@ export function Logout({ }) { return ( - - +
- +
); } diff --git a/packages/keychain/src/components/connect/MediaViewer.tsx b/packages/keychain/src/components/connect/MediaViewer.tsx deleted file mode 100644 index 608f2365b..000000000 --- a/packages/keychain/src/components/connect/MediaViewer.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Flex, Spinner } from "@chakra-ui/react"; -import Image, { ImageProps } from "next/image"; -import Script from "next/script"; -import { useState } from "react"; - -export function MediaViewer({ - height, - width, - src, - alt = "Media viewer", -}: { - src: string; -} & Pick) { - const ext = src?.split(".").pop(); - const [isLoading, setIsLoading] = useState(true); - - if (ext === "glb") { - return ( - <> -