From 5bf8d30fad8cbf4887eedcc7d98b434e6637c121 Mon Sep 17 00:00:00 2001 From: Junichi Sugiura Date: Tue, 11 Jun 2024 14:23:57 +0200 Subject: [PATCH] Add layout container variant (#348) * Hide close button on full page Move header banner to PortalBanner Get chainId dand controller via context Introduce layout component submodule Add content layout component Add layout context Style connect variant banner * Fix close button condition --- .../src/components/StarknetProvider.tsx | 2 +- .../src/components/Container/Header/index.tsx | 145 ----- .../src/components/DeploymentRequired.tsx | 61 +- .../keychain/src/components/Execute/Call.tsx | 2 - .../keychain/src/components/Execute/Fees.tsx | 16 +- .../{LowEth.tsx => InsufficientFunds.tsx} | 19 +- .../keychain/src/components/Execute/index.tsx | 90 ++- .../keychain/src/components/PortalBanner.tsx | 39 -- .../src/components/PortalFooter/index.tsx | 153 ----- .../keychain/src/components/Quests/Card.tsx | 67 --- .../src/components/Quests/Details.tsx | 218 -------- .../src/components/Quests/Overview.tsx | 79 --- .../keychain/src/components/Quests/index.tsx | 98 ---- .../keychain/src/components/Quests/types.ts | 5 - packages/keychain/src/components/Redeploy.tsx | 14 +- .../keychain/src/components/SignMessage.tsx | 83 ++- .../src/components/bridge/BridgeEth.tsx | 8 +- .../src/components/bridge/Transactions.tsx | 4 +- .../src/components/connect/Authenticate.tsx | 86 --- .../connect/Authenticate/Unsupported.tsx | 33 ++ .../components/connect/Authenticate/index.tsx | 72 +++ .../src/components/connect/BannerImage.tsx | 40 -- .../src/components/connect/CreateSession.tsx | 52 +- .../keychain/src/components/connect/Login.tsx | 30 +- .../src/components/{ => connect}/Logout.tsx | 10 +- .../src/components/connect/MediaViewer.tsx | 54 -- .../src/components/connect/Signup.tsx | 30 +- .../connect/StarterPack/Carousel.tsx | 152 ----- .../components/connect/StarterPack/index.tsx | 257 --------- .../src/components/connect/Unsupported.tsx | 14 - .../keychain/src/components/connect/index.tsx | 2 +- .../keychain/src/components/connect/utils.ts | 3 +- packages/keychain/src/components/index.ts | 6 - .../keychain/src/components/layout/Banner.tsx | 149 +++++ .../Container/Header/AccountMenu.tsx | 0 .../Container/Header/NetworkButton.tsx | 0 .../layout/Container/Header/index.tsx | 88 +++ .../{ => layout}/Container/index.tsx | 74 +-- .../src/components/layout/Content.tsx | 11 + .../Footer}/SessionDetails.tsx | 0 .../Footer}/TransactionSummary.tsx | 11 +- .../src/components/layout/Footer/index.tsx | 138 +++++ .../keychain/src/components/layout/index.ts | 4 + packages/keychain/src/generated/graphql.ts | 529 +----------------- packages/keychain/src/hooks/connection.tsx | 26 + packages/keychain/src/hooks/quests.tsx | 174 ------ .../keychain/src/pages/accountQuests.graphql | 74 --- packages/keychain/src/pages/authenticate.tsx | 4 +- .../keychain/src/pages/claim/[gameId].tsx | 21 - packages/keychain/src/pages/index.tsx | 20 +- packages/keychain/src/pages/login.tsx | 9 +- packages/keychain/src/pages/pending.tsx | 25 +- packages/keychain/src/pages/quests.tsx | 1 - packages/keychain/src/pages/signup.tsx | 2 +- .../keychain/src/pages/slot/auth/consent.tsx | 15 +- .../keychain/src/pages/slot/auth/failure.tsx | 27 +- .../keychain/src/pages/slot/auth/index.tsx | 3 +- .../keychain/src/pages/slot/auth/success.tsx | 11 +- .../keychain/src/pages/starterpack.graphql | 73 --- .../ui/src/components/icons/state/Key.tsx | 28 + .../ui/src/components/icons/state/index.ts | 1 + 61 files changed, 830 insertions(+), 2632 deletions(-) delete mode 100644 packages/keychain/src/components/Container/Header/index.tsx rename packages/keychain/src/components/Execute/{LowEth.tsx => InsufficientFunds.tsx} (89%) delete mode 100644 packages/keychain/src/components/PortalBanner.tsx delete mode 100644 packages/keychain/src/components/PortalFooter/index.tsx delete mode 100644 packages/keychain/src/components/Quests/Card.tsx delete mode 100644 packages/keychain/src/components/Quests/Details.tsx delete mode 100644 packages/keychain/src/components/Quests/Overview.tsx delete mode 100644 packages/keychain/src/components/Quests/index.tsx delete mode 100644 packages/keychain/src/components/Quests/types.ts delete mode 100644 packages/keychain/src/components/connect/Authenticate.tsx create mode 100644 packages/keychain/src/components/connect/Authenticate/Unsupported.tsx create mode 100644 packages/keychain/src/components/connect/Authenticate/index.tsx delete mode 100644 packages/keychain/src/components/connect/BannerImage.tsx rename packages/keychain/src/components/{ => connect}/Logout.tsx (71%) delete mode 100644 packages/keychain/src/components/connect/MediaViewer.tsx delete mode 100644 packages/keychain/src/components/connect/StarterPack/Carousel.tsx delete mode 100644 packages/keychain/src/components/connect/StarterPack/index.tsx delete mode 100644 packages/keychain/src/components/connect/Unsupported.tsx create mode 100644 packages/keychain/src/components/layout/Banner.tsx rename packages/keychain/src/components/{ => layout}/Container/Header/AccountMenu.tsx (100%) rename packages/keychain/src/components/{ => layout}/Container/Header/NetworkButton.tsx (100%) create mode 100644 packages/keychain/src/components/layout/Container/Header/index.tsx rename packages/keychain/src/components/{ => layout}/Container/index.tsx (56%) create mode 100644 packages/keychain/src/components/layout/Content.tsx rename packages/keychain/src/components/{PortalFooter => layout/Footer}/SessionDetails.tsx (100%) rename packages/keychain/src/components/{PortalFooter => layout/Footer}/TransactionSummary.tsx (87%) create mode 100644 packages/keychain/src/components/layout/Footer/index.tsx create mode 100644 packages/keychain/src/components/layout/index.ts delete mode 100644 packages/keychain/src/hooks/quests.tsx delete mode 100644 packages/keychain/src/pages/accountQuests.graphql delete mode 100644 packages/keychain/src/pages/claim/[gameId].tsx delete mode 100644 packages/keychain/src/pages/quests.tsx delete mode 100644 packages/keychain/src/pages/starterpack.graphql create mode 100644 packages/ui/src/components/icons/state/Key.tsx 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 ( - <> -