diff --git a/.env.sample b/.env.sample index b05e75d..825910a 100644 --- a/.env.sample +++ b/.env.sample @@ -1,11 +1,9 @@ # Located here https://console.circle.com/wallets/user/configurator NEXT_PUBLIC_APP_ID="[APP_ID goes here]" # hosted backend url -NEXT_PUBLIC_BASE_URL="http://localhost:8080/pw-user-controlled/foundational" -# Hosted base path -NEXT_PUBLIC_BASE_PATH="/pw-user-controlled/foundational" +NEXT_PUBLIC_BASE_URL="http://localhost:8080" # canonical hosted url -NEXTAUTH_URL="http://localhost:3000/pw-user-controlled/foundational/api/auth" +NEXTAUTH_URL="http://localhost:3000" NODE_ENV=development diff --git a/README.md b/README.md index c852b5c..accb753 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Circle User-Controlled Wallets Sample App - Frontend UI -Check out the [live demo](http://sample-app.circle.com/pw-user-controlled/foundational) first to see what to expect! +Check out the [live demo](https://user-controlled-wallets-sample-app.circle.com/) first to see what to expect! ## Overview @@ -14,13 +14,16 @@ This is a sample frontend UI that plays a part in the larger Sample App project. 2. Install [nvm](https://github.com/nvm-sh/nvm), [openssl](https://formulae.brew.sh/formula/openssl@3), and [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable), these are required development tools. +3. **_Important:_** Set up the [Sample Server](https://github.com/circlefin/w3s-sample-user-controlled-server-node) as well to get the end-to-end experience. Please be aware that the [SDK user token](https://developers.circle.com/w3s/reference/getusertoken) will expire after 60 minutes. + ## Configure the Sample App -Run `yarn env:config`, and you will see a `.env` file generated in the root directory +1. Run `yarn env:config`, and you will see a `.env` file generated in the root directory +2. Paste your [App ID](https://console.circle.com/wallets/user/configurator) with `[APP_ID goes here]` in the `.env` file. ## Get Started -Run the following commands to start the UI at `localhost:3000/pw-user-controlled/foundational`: +Run the following commands to start the UI at `localhost:3000`: ``` bash nvm use @@ -32,15 +35,64 @@ yarn dev 2. `yarn install`: install dependencies. 3. `yarn dev`: run the server, hot reload is supported. -Set up the [Sample Server](https://github.com/circlefin/w3s-sample-user-controlled-server-node) as well to get the end-to-end experience. Please be aware that the [SDK user token](https://developers.circle.com/w3s/reference/getusertoken) will expire after 60 minutes. - ## Architecture -We use [Next.js](https://nextjs.org/) as [React](https://react.dev/) framework and [Joy UI](https://mui.com/joy-ui/getting-started/) as React component library. - The frontend UI will play the role as `Your Application`, see [details](). ![image](https://files.readme.io/a2a1678-SDK_UserC_Wallets_Sequence__Detailed2x.png) +## Code Structure + +We use [Next.js](https://nextjs.org/) as [React](https://react.dev/) framework and [Joy UI](https://mui.com/joy-ui/getting-started/) as React component library. + +- The main logic to interact with the Circle Web3 Services Web SDK is going to be in our client side component in `app/components`: + - `providers/W3sProvider.tsx`: holds the value to setup and instantiate a SDK instance. Part of the setup is authorizing with the App ID, + + ```javascript + webClient?.setAppSettings({ + appId, + }); + ``` + + setting up the forgot pin callback, + + ```javascript + webClient?.setOnForgotPin(async () => { + const response = await axios.post<{ challengeId: string }>( + "/users/pin/restore", + ); + if (response.data) { + webClient.execute(response.data.challengeId); + } + }); + ``` + + and authenticating with the user token + encryption key. + + ```javascript + client.setAuthentication({ + userToken: currUser.userToken, + encryptionKey: currUser.encryptionKey, + }); + ``` + + - `Authentication/AuthenticationForm.tsx` has an example of executing a challenge ID and cutomizing behavior based off a successful execution. + + ```javascript + client.execute(session.user.challengeId, (error, result) => { + if (error) { + setFormMessage("An error occurred on PIN Setup. Please try again."); + } else if (result) { + router.push("/wallets"); + } + }); + ``` + +- `app/(pages)` contains all the server side pages of this Next.js application. Any directory wrapped in `()` is a [route grouping](https://nextjs.org/docs/app/building-your-application/routing/route-groups). + - `(authorized)/`: all server side pages that can only be viewed if the user has a valid session. Check out `(authorized)/layout.ts` to see session validation. +- The above are the most important files to get an understanding of this application. All other files are specific to this application and not crucial to using Circle Web3 Services Web SDK. + +**Happy Coding!** + ## Additional Resources - [Circle Web3 Services Web SDK](https://developers.circle.com/w3s/docs/web-sdk-ui-customizations) supports UI customization, check [more examples](https://github.com/circlefin/w3s-pw-web-sdk). diff --git a/app/(pages)/(authorized)/wallets/[id]/layout.tsx b/app/(pages)/(authorized)/wallets/[id]/layout.tsx index 6582620..563c265 100644 --- a/app/(pages)/(authorized)/wallets/[id]/layout.tsx +++ b/app/(pages)/(authorized)/wallets/[id]/layout.tsx @@ -51,7 +51,7 @@ export default function WalletLayout({ onClick={() => signOut({ redirect: true, - callbackUrl: "/signin", + callbackUrl: process.env.NEXTAUTH_URL, }) } > diff --git a/app/(pages)/(authorized)/wallets/layout.tsx b/app/(pages)/(authorized)/wallets/layout.tsx index 5314830..58d7758 100644 --- a/app/(pages)/(authorized)/wallets/layout.tsx +++ b/app/(pages)/(authorized)/wallets/layout.tsx @@ -8,7 +8,9 @@ export default async function RootLayout({ children: React.ReactNode; }>) { const session = await getServerSession(authOptions); - const isValidOnboardStatus = session ? await validOnboardStatus(session) : false; + const isValidOnboardStatus = session + ? await validOnboardStatus(session) + : false; if (!session || !isValidOnboardStatus) { redirect("/signin"); diff --git a/app/(pages)/layout.tsx b/app/(pages)/layout.tsx index 0b3a891..082e1b7 100644 --- a/app/(pages)/layout.tsx +++ b/app/(pages)/layout.tsx @@ -22,13 +22,12 @@ export default function RootLayout({ children: React.ReactNode; }>) { const queryClient = new QueryClient(); - const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; return (
- + @@ -36,7 +35,7 @@ export default function RootLayout({
Circle Logo = ({ id }) => { const { data: transactions, isLoading } = useTransactionsQuery(id); const { data: wallet } = useWallet(id); - const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; if (!isLoading && transactions?.length === 0) { return ( <> no tokens = ({ id }) => { const nativeTokenInfo = tokenHelper(mainBalance?.token.name); const blockchainInfo = blockchainMeta(walletData?.data.wallet.blockchain); - const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; return ( <> @@ -87,7 +86,7 @@ export const WalletDetails: React.FC = ({ id }) => { <> no tokens { const imageSymbol = tokenHelper(tokenName); const router = useRouter(); const date = useMemo(() => new Date(), []); - const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; - return ( <>
Success { const finalSum = amounts.reduce((sum, amount) => Number(amount) + sum, 0); @@ -57,11 +55,11 @@ export const blockchainIcon = (blockchain: string | undefined) => { switch (blockchain) { case "MATIC-AMOY": case "MATIC-MUMBAI": - return `${basePath}/Poly.svg`; + return `/Poly.svg`; case "ETH-SEPOLIA": - return `${basePath}/Eth.svg`; + return `/Eth.svg`; case "AVAX-FUJI": - return `${basePath}/Avax.svg`; + return `/Avax.svg`; default: return ""; } @@ -72,19 +70,19 @@ export const blockchainMeta = (blockchain: string | undefined) => { switch (blockchain) { case "MATIC-AMOY": return { - svg: `${basePath}/Poly.svg`, + svg: `/Poly.svg`, testnet: "Matic Amoy Testnet", nativeTokenName: "AmoyMATIC", }; case "MATIC-MUMBAI": return { - svg: `${basePath}/Poly.svg`, + svg: `/Poly.svg`, testnet: "Matic Mumbai Testnet", nativeTokenName: "MumbaiMATIC", }; case "ETH-SEPOLIA": return { - svg: `${basePath}/Eth.svg`, + svg: `/Eth.svg`, testnet: "Ethereum Sepolia Testnet", nativeTokenName: "SepoliaEth", }; @@ -97,32 +95,31 @@ export const blockchainMeta = (blockchain: string | undefined) => { } }; -// TODO: need to update these token helpers. export const tokenHelper = (tokenName: string | undefined) => { switch (tokenName) { case "Ethereum-Sepolia": return { - svg: `${basePath}/Eth.svg`, + svg: `/Eth.svg`, symbol: "ETH", name: "SepoliaETH", }; case "Polygon-Amoy": return { - svg: `${basePath}/Poly.svg`, + svg: `/Poly.svg`, symbol: "MATIC-AMOY", name: "AmoyMATIC", }; case "Polygon-Mumbai": case "Polygon": return { - svg: `${basePath}/Poly.svg`, + svg: `/Poly.svg`, symbol: "MATIC-MUMBAI", name: "MumbaiMATIC", }; case "USD Coin": case "USDC": return { - svg: `${basePath}/USDC.svg`, + svg: `/USDC.svg`, symbol: "USDC", name: "USDC", }; @@ -168,7 +165,6 @@ export const validOnboardStatus = async (session: Session): Promise => } } -// move to configs export const authOptions: NextAuthOptions = { secret: process.env.NEXTAUTH_SECRET, providers: [ @@ -212,7 +208,9 @@ export const authOptions: NextAuthOptions = { }); if (userInfo) { if (userInfo.status === 201) { - throw Error("This email address has already been used, please sign in"); + throw Error( + "This email address has already been used, please sign in", + ); } // Any object returned will be saved in `user` property of the JWT const user = { @@ -243,3 +241,4 @@ export const authOptions: NextAuthOptions = { }, debug: process.env.NODE_ENV !== "production", }; + diff --git a/next.config.js b/next.config.js index 7fb8e71..29c863e 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,5 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - basePath: "/pw-user-controlled/foundational", async redirects() { return [ { diff --git a/package.json b/package.json index 3749a9f..9f030f0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "env:config": "cp .env.sample .env && printf NEXTAUTH_SECRET=$(openssl rand -base64 32) >> .env", - "dev": "open http://localhost:3000/pw-user-controlled/foundational && next dev", + "dev": "open http://localhost:3000 && next dev", "build": "next build", "start": "next start", "lint": "next lint"