diff --git a/packages/keychain/__image_snapshots__/components-deploycontroller--default-chromium.png b/packages/keychain/__image_snapshots__/components-deploycontroller--default-chromium.png index a89e22673..5068ffb7b 100644 Binary files a/packages/keychain/__image_snapshots__/components-deploycontroller--default-chromium.png and b/packages/keychain/__image_snapshots__/components-deploycontroller--default-chromium.png differ diff --git a/packages/keychain/__image_snapshots__/components-funding--default-chromium.png b/packages/keychain/__image_snapshots__/components-funding--default-chromium.png index 91093c2d2..dcef7caa3 100644 Binary files a/packages/keychain/__image_snapshots__/components-funding--default-chromium.png and b/packages/keychain/__image_snapshots__/components-funding--default-chromium.png differ diff --git a/packages/keychain/__image_snapshots__/components-funding-purchasecredits--default-chromium.png b/packages/keychain/__image_snapshots__/components-funding-purchasecredits--default-chromium.png index c6f291024..3d04380bd 100644 Binary files a/packages/keychain/__image_snapshots__/components-funding-purchasecredits--default-chromium.png and b/packages/keychain/__image_snapshots__/components-funding-purchasecredits--default-chromium.png differ diff --git a/packages/keychain/src/components/app.tsx b/packages/keychain/src/components/app.tsx index 0f580bbef..80a27a623 100644 --- a/packages/keychain/src/components/app.tsx +++ b/packages/keychain/src/components/app.tsx @@ -7,6 +7,7 @@ import { Success } from "./success"; import { Pending } from "./pending"; import { Consent, Slot } from "./slot"; import { OcclusionDetector } from "./OcclusionDetector"; +import { Fund } from "./slot/fund"; export function App() { return ( @@ -19,6 +20,7 @@ export function App() { } /> }> } /> + } /> } /> } /> diff --git a/packages/keychain/src/components/funding/Balance.tsx b/packages/keychain/src/components/funding/Balance.tsx index 341ee94fb..33797d083 100644 --- a/packages/keychain/src/components/funding/Balance.tsx +++ b/packages/keychain/src/components/funding/Balance.tsx @@ -16,8 +16,10 @@ import { } from "@cartridge/utils"; import { useController } from "@/hooks/controller"; +export type BalanceType = "credits" | "eth" | "strk"; + type BalanceProps = { - showBalances: ("credits" | "eth" | "strk")[]; + showBalances: BalanceType[]; }; export function Balance({ showBalances }: BalanceProps) { @@ -26,6 +28,7 @@ export function Balance({ showBalances }: BalanceProps) { username: controller?.username(), interval: 3000, }); + const { data: [eth], } = useERC20Balance({ diff --git a/packages/keychain/src/components/funding/PurchaseCredits.tsx b/packages/keychain/src/components/funding/PurchaseCredits.tsx index 61db5c63c..d73e55c12 100644 --- a/packages/keychain/src/components/funding/PurchaseCredits.tsx +++ b/packages/keychain/src/components/funding/PurchaseCredits.tsx @@ -14,6 +14,7 @@ import { Elements } from "@stripe/react-stripe-js"; import { Appearance, loadStripe } from "@stripe/stripe-js"; import { Balance } from "./Balance"; import CheckoutForm from "./StripeCheckout"; +import { isIframe } from "@cartridge/utils"; const STRIPE_API_PUBKEY = "pk_test_51Kr6IXIS6lliDpf33KnwWDtIjRPWt3eAI9CuSLR6Vvc3GxHEwmSU0iszYbUlgUadSRluGKAFphe3JzltyjPAKiBK00al4RAFQu"; @@ -25,10 +26,11 @@ enum PurchaseState { } type PurchaseCreditsProps = { + isSlot?: boolean; onBack?: () => void; }; -export function PurchaseCredits({ onBack }: PurchaseCreditsProps) { +export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) { const { controller, closeModal } = useConnection(); const [clientSecret, setClientSecret] = useState(""); @@ -82,24 +84,6 @@ export function PurchaseCredits({ onBack }: PurchaseCreditsProps) { }, } as Appearance; - // For when we need to support Payment Links - // useStripePaymentQuery( - // { referenceId }, - // { - // enabled: !!referenceId && !error, - // refetchInterval: REFETCH_INTERVAL, - // retry: MAX_RETRIES, - // onSuccess: () => setState(PurchaseState.SUCCESS), - // onError: () => { - // setError( - // new Error( - // `Payment not received. Please try again. Reference ID: ${referenceId}`, - // ), - // ); - // }, - // }, - // ); - if (state === PurchaseState.STRIPE_CHECKOUT) { return ( @@ -150,7 +139,7 @@ export function PurchaseCredits({ onBack }: PurchaseCreditsProps) { /> )} - {state === PurchaseState.SUCCESS && ( + {state === PurchaseState.SUCCESS && isIframe() && ( diff --git a/packages/keychain/src/components/funding/index.tsx b/packages/keychain/src/components/funding/index.tsx index b03a4ffb0..b86551430 100644 --- a/packages/keychain/src/components/funding/index.tsx +++ b/packages/keychain/src/components/funding/index.tsx @@ -10,7 +10,7 @@ import { } from "@cartridge/ui-next"; import { DepositEth } from "./DepositEth"; import { PurchaseCredits } from "./PurchaseCredits"; -import { Balance } from "./Balance"; +import { Balance, BalanceType } from "./Balance"; const enum FundingState { SHOW_OPTIONS, @@ -19,15 +19,18 @@ const enum FundingState { } export type FundingProps = { - title?: React.ReactElement; + title?: React.ReactElement | string; + isSlot?: boolean; onComplete?: (deployHash?: string) => void; }; -export function Funding({ onComplete, title }: FundingProps) { +export function Funding({ title, isSlot, onComplete }: FundingProps) { const { controller } = useConnection(); const [state, setState] = useState(FundingState.SHOW_OPTIONS); + const showBalances: BalanceType[] = isSlot ? ["credits"] : ["credits", "eth"]; const showCredits = - typeof document !== "undefined" && document.cookie.includes("credits="); + (typeof document !== "undefined" && document.cookie.includes("credits=")) || + isSlot; if (state === FundingState.FUND_ETH) { return ( @@ -40,7 +43,10 @@ export function Funding({ onComplete, title }: FundingProps) { if (state === FundingState.FUND_CREDITS) { return ( - setState(FundingState.SHOW_OPTIONS)} /> + setState(FundingState.SHOW_OPTIONS)} + /> ); } @@ -49,9 +55,10 @@ export function Funding({ onComplete, title }: FundingProps) { title={title || (controller ? `Fund ${controller.username()}` : "")} description={controller && } icon={} + hideNetwork > - +
{showCredits && ( @@ -59,12 +66,14 @@ export function Funding({ onComplete, title }: FundingProps) { Purchase Credits )} - + {!isSlot && ( + + )}
); diff --git a/packages/keychain/src/components/slot/consent.tsx b/packages/keychain/src/components/slot/consent.tsx index 29bc8b803..14194e1a8 100644 --- a/packages/keychain/src/components/slot/consent.tsx +++ b/packages/keychain/src/components/slot/consent.tsx @@ -2,10 +2,11 @@ import Controller from "@/utils/controller"; import { Button } from "@cartridge/ui-next"; import { Container, Footer } from "@/components/layout"; import { useCallback, useEffect } from "react"; -import { useNavigate, useSearchParams } from "react-router-dom"; +import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; export function Consent() { const navigate = useNavigate(); + const { pathname } = useLocation(); const [searchParams] = useSearchParams(); const callback_uri = searchParams.get("callback_uri")!; @@ -25,9 +26,18 @@ export function Consent() { useEffect(() => { if (!Controller.fromStore(import.meta.env.VITE_ORIGIN!)) { - navigate("/slot", { replace: true }); + navigate( + `/slot?returnTo=${encodeURIComponent(pathname)}${ + callback_uri + ? `&callback_uri=${encodeURIComponent(callback_uri)}` + : "" + }`, + { + replace: true, + }, + ); } - }, [navigate]); + }, [navigate, callback_uri, pathname]); return ( { + if (!Controller.fromStore(import.meta.env.VITE_ORIGIN!)) { + navigate(`/slot?returnTo=${encodeURIComponent(pathname)}`, { + replace: true, + }); + } + }, [navigate, pathname]); + + return ; +} diff --git a/packages/keychain/src/components/slot/index.tsx b/packages/keychain/src/components/slot/index.tsx index 84da58f69..afcb117bd 100644 --- a/packages/keychain/src/components/slot/index.tsx +++ b/packages/keychain/src/components/slot/index.tsx @@ -23,6 +23,7 @@ export function Slot() { case "/slot/auth/failure": return ; case "/slot/consent": + case "/slot/fund": return ; default: return ; @@ -37,13 +38,17 @@ function Auth() { useEffect(() => { if (user && controller) { - const query = Array.from(searchParams.entries()).reduce( - (prev, [key, val], i) => - i === 0 ? `?${key}=${val}` : `${prev}&${key}=${val}`, - "", - ); + const returnTo = searchParams.get("returnTo"); + const otherParams = Array.from(searchParams.entries()) + .filter(([key]) => key !== "returnTo") + .reduce( + (prev, [key, val], i) => + i === 0 ? `?${key}=${val}` : `${prev}&${key}=${val}`, + "", + ); - navigate(`/slot/consent${query}`, { replace: true }); + const target = returnTo ? `${returnTo}${otherParams}` : "/slot"; + navigate(target, { replace: true }); } }, [user, controller, navigate, searchParams]); diff --git a/packages/keychain/src/hooks/connection.ts b/packages/keychain/src/hooks/connection.ts index 449735ede..5de35e9ef 100644 --- a/packages/keychain/src/hooks/connection.ts +++ b/packages/keychain/src/hooks/connection.ts @@ -107,8 +107,13 @@ export function useConnectionValue() { useEffect(() => { const urlParams = new URLSearchParams(window.location.search); - // Set rpc and origin if we're not embedded (eg Slot auth/session) + // if we're not embedded (eg Slot auth/session) load controller from store and set origin/rpcUrl if (!isIframe()) { + const controller = Controller.fromStore(import.meta.env.VITE_ORIGIN!); + if (controller) { + setController(controller); + } + setOrigin(urlParams.get("origin") || import.meta.env.VITE_ORIGIN); const rpcUrl = urlParams.get("rpc_url"); if (rpcUrl) {