diff --git a/v2/demo-apps/.env.example b/v2/demo-apps/.env.example index 0bc6c0dd..7edeb934 100644 --- a/v2/demo-apps/.env.example +++ b/v2/demo-apps/.env.example @@ -47,5 +47,8 @@ COMPILOT_API_KEY_LANDING_KYB COMPILOT_WORKFLOW_ID_LANDING_KYB NEXT_PUBLIC_LANDING_KYB_COMPANY +COMPILOT_API_KEY_NO_WALLET_KYB +COMPILOT_WORKFLOW_ID_NO_WALLET_KYB + NEXT_PUBLIC_AMOY_HTTP_PROVIDER_URL= NEXT_PUBLIC_SEPOLIA_HTTP_PROVIDER_URL= diff --git a/v2/demo-apps/src/env.mjs b/v2/demo-apps/src/env.mjs index 73d9f0dc..8124a48c 100644 --- a/v2/demo-apps/src/env.mjs +++ b/v2/demo-apps/src/env.mjs @@ -144,6 +144,30 @@ export const env = createEnv({ .describe( "The workflow ID for the ComPilot API -> https://dashboard.compilot.ai/: ComPilot Multichain Demo dApp", ), + + COMPILOT_API_KEY_LANDING_KYB: z + .string() + .describe( + "The API key for the ComPilot API -> https://dashboard.compilot.ai/: ComPilot Landing KYB Demo App", + ), + + COMPILOT_WORKFLOW_ID_LANDING_KYB: z + .string() + .describe( + "The workflow ID for the ComPilot API -> https://dashboard.compilot.ai/: ComPilot Landing KYB Demo App", + ), + + COMPILOT_API_KEY_NO_WALLET_KYB: z + .string() + .describe( + "The API key for the ComPilot API -> https://dashboard.compilot.ai/: ComPilot no wallet KYB Demo App", + ), + + COMPILOT_WORKFLOW_ID_NO_WALLET_KYB: z + .string() + .describe( + "The workflow ID for the ComPilot API -> https://dashboard.compilot.ai/: ComPilot no wallet KYB Demo App", + ), }, /** @@ -216,6 +240,14 @@ export const env = createEnv({ process.env.COMPILOT_API_KEY_MULTICHAIN_DEMO, COMPILOT_WORKFLOW_ID_MULTICHAIN_DEMO: process.env.COMPILOT_WORKFLOW_ID_MULTICHAIN_DEMO, + + COMPILOT_API_KEY_LANDING_KYB: process.env.COMPILOT_API_KEY_LANDING_KYB, + COMPILOT_WORKFLOW_ID_LANDING_KYB: + process.env.COMPILOT_WORKFLOW_ID_LANDING_KYB, + + COMPILOT_API_KEY_NO_WALLET_KYB: process.env.COMPILOT_API_KEY_NO_WALLET_KYB, + COMPILOT_WORKFLOW_ID_NO_WALLET_KYB: + process.env.COMPILOT_WORKFLOW_ID_NO_WALLET_KYB, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. diff --git a/v2/demo-apps/src/features/landing-kyb-without-wallet/Layout/Layout.tsx b/v2/demo-apps/src/features/landing-kyb-without-wallet/Layout/Layout.tsx new file mode 100644 index 00000000..12b22617 --- /dev/null +++ b/v2/demo-apps/src/features/landing-kyb-without-wallet/Layout/Layout.tsx @@ -0,0 +1,18 @@ +import type { ReactNode } from 'react'; + +interface LayoutProps { + children: ReactNode; + header?: ReactNode; +} + +export const Layout = ({ children, header }: LayoutProps) => { + return ( +
+ {header &&
{header}
+ } +
+ {children} +
+
+ ); +}; diff --git a/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/compilotConfig.ts b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/compilotConfig.ts new file mode 100644 index 00000000..5a38c868 --- /dev/null +++ b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/compilotConfig.ts @@ -0,0 +1,26 @@ +import { createAuthAdapter, createConfig } from "@compilot/react-sdk"; +import "@/features/root/configureReactDemoEnv"; +import { useAuthStore } from "./useKybWithoutWalletAuthentication"; + +export const compilotConfig = createConfig({ + authAdapter: createAuthAdapter({ + // This is a fake implementation of the auth adapter + createSession: async () => { + const authState = useAuthStore.getState(); + if (!authState.isAuthenticated || !authState.userId) { + throw new Error("User is not authenticated"); + } + const session = await fetch("/api/landing-kyb-not-wallet/access-token", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userId: authState.userId, + }), + }); + + return session.json(); + }, + }), +}); diff --git a/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/fetchAccessToken.ts b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/fetchAccessToken.ts new file mode 100644 index 00000000..9f64a977 --- /dev/null +++ b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/fetchAccessToken.ts @@ -0,0 +1,31 @@ +interface AccessTokenRequest { + userId: string; +} + +interface AccessTokenResponse { + accessToken: string; +} + +export async function fetchAccessToken( + requestData: AccessTokenRequest, +): Promise { + try { + const response = await fetch(`/api/landing-kyb-not-wallet/access-token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestData), + }); + + if (!response.ok) { + throw new Error("Failed to fetch access token"); + } + + const data = (await response.json()) as AccessTokenResponse; + return data; + } catch (error) { + console.error("Error fetching access token:", error); + throw new Error("Error fetching access token"); + } +} diff --git a/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/useKybWithoutWalletAuthentication.tsx b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/useKybWithoutWalletAuthentication.tsx new file mode 100644 index 00000000..6fc8702b --- /dev/null +++ b/v2/demo-apps/src/features/landing-kyb-without-wallet/identity/useKybWithoutWalletAuthentication.tsx @@ -0,0 +1,89 @@ +import { useMutation } from "@tanstack/react-query"; +import { create } from "zustand"; +import { createJSONStorage, devtools, persist } from "zustand/middleware"; +import { immer } from "zustand/middleware/immer"; +import { fetchAccessToken } from "./fetchAccessToken"; + +export const useMockKybWithoutWallet = () => { + const authStore = useAuthStore((state) => state); + + const logout = useMutation({ + mutationFn: async () => { + await Promise.resolve(authStore.logout()); + }, + }); + + const authenticate = useMutation({ + mutationFn: async (variables: { userId: string }) => { + const response = await fetchAccessToken({ + userId: variables.userId, + }); + const { accessToken } = response; + return { + accessToken, + testUser: variables.userId, + }; + }, + onSuccess: (data) => { + authStore.authenticate(data.accessToken, data.testUser); + }, + onError: (error) => { + console.error(error); + }, + }); + + return { + authenticate, + logout, + accessToken: authStore.accessToken, + isAuthenticated: authStore.isAuthenticated, + isIdentityClientInit: authStore.isIdentityClientInit, + setIsIdentityClientInit: authStore.setIsIdentityClientInit, + userId: authStore.userId, + }; +}; + +interface IAuthStore { + accessToken?: string; + isAuthenticated: boolean; + userId?: string; + isIdentityClientInit: boolean; + setIsIdentityClientInit: (isInit: boolean) => void; + authenticate: (accessToken: string, userId: string) => void; + logout: () => void; +} + +export const useAuthStore = create()( + devtools( + persist( + immer((set) => ({ + accessToken: undefined, + isAuthenticated: false, + isIdentityClientInit: false, + setIsIdentityClientInit: (isInit: boolean) => { + set((state) => { + state.isIdentityClientInit = isInit; + }); + }, + authenticate: (accessToken: string, userId: string) => { + set((state) => { + state.accessToken = accessToken; + state.isAuthenticated = true; + state.userId = userId; + }); + }, + logout: () => { + set((state) => { + state.accessToken = undefined; + state.isAuthenticated = false; + state.userId = undefined; + }); + }, + })), + { + name: "kyb-without-wallet", + storage: createJSONStorage(() => sessionStorage), + }, + ), + ), +); diff --git a/v2/demo-apps/src/pages/api/landing-kyb-not-wallet/access-token.ts b/v2/demo-apps/src/pages/api/landing-kyb-not-wallet/access-token.ts new file mode 100644 index 00000000..1a802502 --- /dev/null +++ b/v2/demo-apps/src/pages/api/landing-kyb-not-wallet/access-token.ts @@ -0,0 +1,37 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { env } from "@/env.mjs"; +import { createSdk } from "@compilot/js-sdk"; + +import "@/features/root/configureNodeDemoEnv"; + +const apiClient = createSdk({ + webhookSecret: env.COMPILOT_WEBHOOK_SECRET_BANK_KYB, + apiKey: env.COMPILOT_API_KEY_NO_WALLET_KYB, +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== "POST") { + res.setHeader("Allow", ["POST"]); + res.status(405).end(`Method ${req.method} Not Allowed`); + return; + } + + try { + // Get the current user ID + // NOTE: This is a simplified example. In a real-world application, you should use a more secure method to get the user ID. + const userId = req.body.userId; + + const authSession = await apiClient.createSession({ + externalCustomerId: userId, + workflowId: env.COMPILOT_WORKFLOW_ID_NO_WALLET_KYB, + }); + + res.status(200).json(authSession); + } catch (error) { + console.error("API call error:", error); + res.status(500).json({ error: "Failed to fetch access token" }); + } +} diff --git a/v2/demo-apps/src/pages/index.tsx b/v2/demo-apps/src/pages/index.tsx index e8395199..bdc45eea 100644 --- a/v2/demo-apps/src/pages/index.tsx +++ b/v2/demo-apps/src/pages/index.tsx @@ -106,6 +106,15 @@ const PROJECTS: AppCardProps[] = [ tags: [VCV_TAG, OFF_TAG, AS_TAG], poweredBy: "/images/poweredBy/green-bank.svg", }, + { + url: "/landing-kyb-not-wallet", + name: "KYB without wallet", + description: + "Short description for this app example that we showcase here. Better to have a few lines of text, so not very short if possible", + image: "/images/apps/banking.svg", + tags: [VCV_TAG, OFF_TAG, AS_TAG], + poweredBy: "/images/poweredBy/green-bank.svg", + }, { url: "/bank-web3", name: "Web3 Banking", diff --git a/v2/demo-apps/src/pages/landing-kyb-not-wallet.tsx b/v2/demo-apps/src/pages/landing-kyb-not-wallet.tsx new file mode 100644 index 00000000..fc67e320 --- /dev/null +++ b/v2/demo-apps/src/pages/landing-kyb-not-wallet.tsx @@ -0,0 +1,102 @@ +import { WagmiProvider } from "wagmi"; +import { Layout } from "@/features/landing-kyb/Layout/Layout"; +import { wagmiConfig } from "@/features/root/web3/wagmiConfig"; +import { + ComPilotProvider, + useOpenWidget, + useDisconnect, +} from "@compilot/react-sdk"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { compilotConfig } from "@/features/landing-kyb-without-wallet/identity/compilotConfig"; +import { useMockKybWithoutWallet } from "@/features/landing-kyb-without-wallet/identity/useKybWithoutWalletAuthentication"; + +const queryClient = new QueryClient(); + +const Home = () => { + return ( + + + + + + + + ); +}; + +const HomeContent = () => { + const openWidget = useOpenWidget(); + const { disconnect } = useDisconnect(); + const companyName = process.env.NEXT_PUBLIC_LANDING_KYB_COMPANY ?? ""; + const { authenticate, isAuthenticated, logout } = useMockKybWithoutWallet(); + + const handleClick = async () => { + try { + await disconnect(); + await new Promise((resolve) => setTimeout(resolve, 100)); + await openWidget.openWidget(); + } catch (error) { + await disconnect(); + } + }; + + return ( + +
+
+
+ {/* Left Column - Title and Button */} +
+

+ {companyName} Verification +

+ + {isAuthenticated ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+
+ ); +}; + +export default Home;