Skip to content

Commit

Permalink
🔄 synced file(s) with circlefin/pw-sample-app-internal (#8)
Browse files Browse the repository at this point in the history
synced local file(s) with
[circlefin/pw-sample-app-internal](https://github.com/circlefin/pw-sample-app-internal).



<details>
<summary>Changed files</summary>
<ul>
<li>synced local <code>README.md</code> with remote
<code>README.md</code></li><li>synced local directory <code>app/</code>
with remote directory <code>app/</code></li><li>synced local
<code>.env.sample</code> with remote
<code>.env.sample</code></li><li>synced local
<code>next.config.js</code> with remote
<code>next.config.js</code></li><li>synced local
<code>package.json</code> with remote <code>package.json</code></li>
</ul>
</details>

---

This PR was created automatically by the
[repo-file-sync-action](https://github.com/BetaHuhn/repo-file-sync-action)
workflow run
[#8942714876](https://github.com/circlefin/pw-sample-app-internal/actions/runs/8942714876)
  • Loading branch information
heondokim authored May 3, 2024
2 parents 2029584 + 04c5787 commit d383483
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 44 deletions.
6 changes: 2 additions & 4 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -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
66 changes: 59 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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](<https://developers.circle.com/w3s/docs/sdk-architecture-for-user-controlled-wallets#sdk-architecture>).
![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).
Expand Down
2 changes: 1 addition & 1 deletion app/(pages)/(authorized)/wallets/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function WalletLayout({
onClick={() =>
signOut({
redirect: true,
callbackUrl: "/signin",
callbackUrl: process.env.NEXTAUTH_URL,
})
}
>
Expand Down
4 changes: 3 additions & 1 deletion app/(pages)/(authorized)/wallets/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
5 changes: 2 additions & 3 deletions app/(pages)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,20 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
const queryClient = new QueryClient();
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";

return (
<html lang="en">
<body className={inter.className}>
<div id="__next">
<SessionProvider basePath={`${basePath}/api/auth`}>
<SessionProvider>
<QueryClientProvider client={queryClient}>
<W3sProvider>
<ThemeRegistry options={{ key: "joy" }}>
<div className="flex justify-center items-center h-screen px-4">
<div className="max-w-xl w-full h-full max-h-[660px] border border-solid border-gray-200 rounded-lg shadow-lg flex flex-col relative overflow-hidden">
<span className="bg-purple-500 text-white p-3 font-medium flex items-center gap-x-2.5">
<Image
src={`${basePath}/CircleLogo.svg`}
src={`/CircleLogo.svg`}
alt="Circle Logo"
width={20}
height={20}
Expand Down
6 changes: 4 additions & 2 deletions app/(pages)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { authOptions, validOnboardStatus } from "@/app/shared/utils";
import { AuthenticationForm } from "../../components/Authentication/AuthenticationForm";
import { AuthenticationForm } from "@/app/components";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";

export default async function SigninPage() {
const session = await getServerSession(authOptions);
const isValidOnboardStatus = session ? await validOnboardStatus(session) : false;
const isValidOnboardStatus = session
? await validOnboardStatus(session)
: false;

if (session && isValidOnboardStatus) {
redirect("/wallets");
Expand Down
4 changes: 3 additions & 1 deletion app/(pages)/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { AuthenticationForm } from "@/app/components";

export default async function SignupPage() {
const session = await getServerSession(authOptions);
const isValidOnboardStatus = session ? await validOnboardStatus(session) : false;
const isValidOnboardStatus = session
? await validOnboardStatus(session)
: false;

if (session && isValidOnboardStatus) {
redirect("/wallets");
Expand Down
3 changes: 1 addition & 2 deletions app/containers/WalletActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ interface WalletActivityProps {
export const WalletActivity: React.FC<WalletActivityProps> = ({ 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 (
<>
<Image
alt="no tokens"
src={`${basePath}/NoActivity.svg`}
src={`/NoActivity.svg`}
height={80}
width={80}
className="mx-auto mt-4 mb-6"
Expand Down
3 changes: 1 addition & 2 deletions app/containers/WalletDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const WalletDetails: React.FC<WalletDetailsProps> = ({ id }) => {

const nativeTokenInfo = tokenHelper(mainBalance?.token.name);
const blockchainInfo = blockchainMeta(walletData?.data.wallet.blockchain);
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";

return (
<>
Expand Down Expand Up @@ -87,7 +86,7 @@ export const WalletDetails: React.FC<WalletDetailsProps> = ({ id }) => {
<>
<Image
alt="no tokens"
src={`${basePath}/NoTokens.svg`}
src={`/NoTokens.svg`}
height={120}
width={120}
className="mx-auto"
Expand Down
4 changes: 1 addition & 3 deletions app/containers/Wallets/Send/SendTokenSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ export const SendTokenSummary: React.FC = () => {
const imageSymbol = tokenHelper(tokenName);
const router = useRouter();
const date = useMemo(() => new Date(), []);
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";

return (
<>
<Content>
<div className="flex flex-col items-center mb-4">
<Image
className="mb-4"
src={`${basePath}/Success.gif`}
src={`/Success.gif`}
width={80}
height={80}
alt="Success"
Expand Down
31 changes: 15 additions & 16 deletions app/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { NextAuthOptions, Session, User } from "next-auth";
import { axios } from "@/app/axios";
import CredentialsProvider from "next-auth/providers/credentials";
import { GasFeeObject, Transaction, TransactionStateEnum, TransactionTypeEnum } from "./types";

const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
import CredentialsProvider from "next-auth/providers/credentials";

export const calculateSum = (amounts: string[]): string => {
const finalSum = amounts.reduce((sum, amount) => Number(amount) + sum, 0);
Expand Down Expand Up @@ -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 "";
}
Expand All @@ -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",
};
Expand All @@ -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",
};
Expand Down Expand Up @@ -168,7 +165,6 @@ export const validOnboardStatus = async (session: Session): Promise<boolean> =>
}
}

// move to configs
export const authOptions: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -243,3 +241,4 @@ export const authOptions: NextAuthOptions = {
},
debug: process.env.NODE_ENV !== "production",
};

1 change: 0 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: "/pw-user-controlled/foundational",
async redirects() {
return [
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit d383483

Please sign in to comment.