Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions v2/demo-apps/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
32 changes: 32 additions & 0 deletions v2/demo-apps/src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
},

/**
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ReactNode } from 'react';

interface LayoutProps {
children: ReactNode;
header?: ReactNode;
}

export const Layout = ({ children, header }: LayoutProps) => {
return (
<div className="min-h-screen bg-white" >
{header && <header>{header} </header>
}
<main>
{children}
</main>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -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();
},
}),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
interface AccessTokenRequest {
userId: string;
}

interface AccessTokenResponse {
accessToken: string;
}

export async function fetchAccessToken(
requestData: AccessTokenRequest,
): Promise<AccessTokenResponse> {
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");
}
}
Original file line number Diff line number Diff line change
@@ -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<IAuthStore>()(
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),
},
),
),
);
Original file line number Diff line number Diff line change
@@ -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" });
}
}
9 changes: 9 additions & 0 deletions v2/demo-apps/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
102 changes: 102 additions & 0 deletions v2/demo-apps/src/pages/landing-kyb-not-wallet.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<ComPilotProvider config={compilotConfig}>
<HomeContent />
</ComPilotProvider>
</QueryClientProvider>
</WagmiProvider>
);
};

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 (
<Layout>
<div className="absolute top-0 z-20 h-screen w-screen bg-white">
<div className="fixed top-1/2 w-full">
<div className="mx-auto flex w-[1200px] justify-center gap-8">
{/* Left Column - Title and Button */}
<div className="flex flex-col items-center pt-6">
<h1 className="text-[24px] font-bold leading-[32px] text-black">
{companyName} Verification
</h1>

{isAuthenticated ? (
<div>
<button
type="button"
className="mt-8 h-14 w-full rounded-2xl border-2 border-[#E6D5F7]
bg-white text-center text-xl text-black
transition-all duration-300 hover:bg-[#E6D5F7]"
onClick={() => {
void handleClick();
}}
>
Start Verification
</button>

<button
type="button"
className="mt-8 h-14 w-full rounded-2xl border-2 border-[#E6D5F7]
bg-white text-center text-xl text-black
transition-all duration-300 hover:bg-[#E6D5F7]"
onClick={() => {
logout.mutate();
}}
>
Log out user
</button>
</div>
) : (
<button
type="button"
className="mt-8 h-14 w-full rounded-2xl border-2 border-[#E6D5F7]
bg-white text-center text-xl text-black
transition-all duration-300 hover:bg-[#E6D5F7]"
onClick={() => {
const randomUserId = crypto.randomUUID();
authenticate.mutate({ userId: randomUserId });
}}
>
Login user
</button>
)}
</div>
</div>
</div>
</div>
</Layout>
);
};

export default Home;