From c61236559792bb8f4e17f92205968b66d03bc6c3 Mon Sep 17 00:00:00 2001 From: Maksym Date: Wed, 18 Sep 2024 18:36:24 +0300 Subject: [PATCH] Proof requests demo (#71) * query-identity generator * fix fetching proof result * remove callback url from qr request body * fix deep links url * add deploy from query-id-test branch * fix deep links url * unify qr-code interactions * Update proof requests demo UI * Chore CI --------- Co-authored-by: lukachi Co-authored-by: chabanyknikita <92546152+chabanyknikita@users.noreply.github.com> --- .env-development | 1 + .env-production | 1 + src/config.ts | 2 + src/enums/routes.ts | 1 + src/pages/ProofRequestsDemo/index.tsx | 404 ++++++++++++++++++++++++++ src/routes.tsx | 5 + src/vite-env.d.ts | 1 + 7 files changed, 415 insertions(+) create mode 100644 src/pages/ProofRequestsDemo/index.tsx diff --git a/.env-development b/.env-development index eb025a83..e63f6ba8 100644 --- a/.env-development +++ b/.env-development @@ -5,3 +5,4 @@ VITE_APP_NAME=Rarime App VITE_APP_HOST_URL=https://app.stage.rarime.com VITE_DEFAULT_CHAIN=SEPOLIA VITE_API_URL=https://api.orgs.app.stage.rarime.com +VITE_VERIFICATOR_API_URL=https://api.orgs.app.stage.rarime.com diff --git a/.env-production b/.env-production index ce3c3ba8..ba4a3afc 100644 --- a/.env-production +++ b/.env-production @@ -5,3 +5,4 @@ VITE_APP_NAME=Rarime App VITE_APP_HOST_URL=https://app.rarime.com VITE_DEFAULT_CHAIN=POLYGON VITE_API_URL=https://api.app.rarime.com +VITE_VERIFICATOR_API_URL=https://api.app.rarime.com diff --git a/src/config.ts b/src/config.ts index 678a567c..7ccda593 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,7 @@ export type Config = { APP_NAME: string APP_HOST_URL: string API_URL: string + VERIFICATOR_API_URL: string BUILD_VERSION: string SUPPORTED_CHAINS_DETAILS: SupportedChainsDetails DEFAULT_CHAIN: SupportedChains @@ -27,6 +28,7 @@ export const config: Config = { APP_NAME: import.meta.env.VITE_APP_NAME, APP_HOST_URL: import.meta.env.VITE_APP_HOST_URL, API_URL: import.meta.env.VITE_API_URL, + VERIFICATOR_API_URL: import.meta.env.VITE_VERIFICATOR_API_URL, BUILD_VERSION: packageJson.version || import.meta.env.VITE_APP_BUILD_VERSION, SUPPORTED_CHAINS_DETAILS, DEFAULT_CHAIN: import.meta.env.VITE_DEFAULT_CHAIN || FALLBACK_DEFAULT_CHAIN, diff --git a/src/enums/routes.ts b/src/enums/routes.ts index 8b1b7c0a..319ba23c 100644 --- a/src/enums/routes.ts +++ b/src/enums/routes.ts @@ -40,4 +40,5 @@ export enum RoutePaths { RewardsInvitationAlias = '/r/:code', DownloadApp = '/download-app', + ProofRequestsDemo = '/proof-requests-demo', } diff --git a/src/pages/ProofRequestsDemo/index.tsx b/src/pages/ProofRequestsDemo/index.tsx new file mode 100644 index 00000000..7b19e42a --- /dev/null +++ b/src/pages/ProofRequestsDemo/index.tsx @@ -0,0 +1,404 @@ +import { JsonApiClient } from '@distributedlab/jac' +import { + Button, + Divider, + FormControl, + Link, + List, + Paper, + Stack, + Typography, + useTheme, +} from '@mui/material' +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' +import { Controller } from 'react-hook-form' +import { QRCode } from 'react-qrcode-logo' +import { v4 as uuid } from 'uuid' + +import { config } from '@/config' +import { ErrorHandler } from '@/helpers' +import { useForm } from '@/hooks' +import { UiSwitch, UiTextField } from '@/ui' + +const apiClient = new JsonApiClient({ + baseUrl: config.VERIFICATOR_API_URL, +}) + +enum DemoSteps { + Intro, + ProofAttributes, + QrCode, + VerificationStatus, +} + +enum VerificationStatuses { + NotVerified = 'not_verified', + Verified = 'verified', + FailedVerification = 'failed_verification', + UniquenessCheckFailed = 'uniqueness_check_failed', +} + +export default function ProofRequestsDemo() { + const [id, setId] = useState('') + const [deepLink, setDeepLink] = useState('') + const [step, setStep] = useState(DemoSteps.Intro) + const [intervalId, setIntervalId] = useState(-1) + const [verificationStatus, setVerificationStatus] = useState( + VerificationStatuses.NotVerified, + ) + + useEffect(() => { + if (step === DemoSteps.QrCode) { + const intervalId = window.setInterval(checkVerificationStatus, 10000) + setIntervalId(intervalId) + } else { + window.clearInterval(intervalId) + setIntervalId(-1) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [step]) + + async function checkVerificationStatus() { + try { + const { data } = await apiClient.get<{ + id: string + type: string + status: VerificationStatuses + }>(`/integrations/verificator-svc/private/verification-status/${id}`) + + if (data.status !== VerificationStatuses.NotVerified) { + setVerificationStatus(data.status) + setStep(DemoSteps.VerificationStatus) + } + } catch (error) { + console.error(error) + } + } + + switch (step) { + case DemoSteps.Intro: + return setStep(DemoSteps.ProofAttributes)} /> + case DemoSteps.ProofAttributes: + return ( + { + setId(id) + setDeepLink(deepLink) + setStep(DemoSteps.QrCode) + }} + /> + ) + case DemoSteps.QrCode: + return + case DemoSteps.VerificationStatus: + return ( + { + setVerificationStatus(VerificationStatuses.NotVerified) + setId('') + setDeepLink('') + setStep(DemoSteps.Intro) + }} + /> + ) + } +} + +function StepView({ + title, + subtitle, + children, +}: { + title: string + subtitle: string + children: ReactNode +}) { + const { palette } = useTheme() + + return ( + + + + {title} + + + {subtitle} + + + {children} + + ) +} + +function IntroStep({ onStart }: { onStart: () => void }) { + const { palette } = useTheme() + + return ( + + + + + The system contains the following components: + + + + 3rd Party Mobile Apps and Backend: external system that integrates + the Proof of Passport + + + RariMe App: mobile app that generates the proofs + + + Verificator-svc: a service that incapsulates ZK proof verification. + Self-hosted by the 3rd party. + + + + + + + + + + ) +} + +enum FieldNames { + Uniqueness = 'uniqueness', + MinimumAge = 'minimumAge', + Nationality = 'nationality', + EventId = 'eventId', +} + +const DEFAULT_VALUES = { + [FieldNames.Uniqueness]: true, + [FieldNames.MinimumAge]: '18', + [FieldNames.Nationality]: 'UKR', + [FieldNames.EventId]: '12345678900987654321', +} + +function ProofAttributesStep({ onSubmit }: { onSubmit: (id: string, deepLink: string) => void }) { + const { handleSubmit, control, isFormDisabled, getErrorMessage, disableForm, enableForm } = + useForm(DEFAULT_VALUES, yup => + yup.object().shape({ + [FieldNames.Uniqueness]: yup.boolean(), + [FieldNames.MinimumAge]: yup.string(), + [FieldNames.Nationality]: yup.string(), + [FieldNames.EventId]: yup.string().required(), + }), + ) + + const submit = useCallback( + async (formData: typeof DEFAULT_VALUES) => { + disableForm() + + try { + const minimumAge = Number(formData[FieldNames.MinimumAge]) + const nationality = formData[FieldNames.Nationality] + + const { data } = await apiClient.post<{ + id: string + type: string + callback_url: string + get_proof_params: string + }>('/integrations/verificator-svc/private/verification-link', { + body: { + data: { + id: `${uuid()}@gmail.com`, + type: 'user', + attributes: { + age_lower_bound: minimumAge ? minimumAge : undefined, + uniqueness: Boolean(formData[FieldNames.Uniqueness]), + nationality: nationality ? nationality : undefined, + event_id: formData[FieldNames.EventId], + }, + }, + }, + }) + + const newUrl = new URL('rarime://external') + newUrl.searchParams.append('type', 'proof-request') + newUrl.searchParams.append('proof_params_url', data.get_proof_params) + + onSubmit(data.id, newUrl.href) + } catch (error) { + ErrorHandler.process(error) + } + + enableForm() + }, + [disableForm, enableForm, onSubmit], + ) + + return ( + + + ( + + + + )} + /> + + + ( + + + + )} + /> + ( + + + + )} + /> + + ( + + + + )} + /> + + + + ) +} + +function QrCodeStep({ deepLink }: { deepLink: string }) { + const { palette } = useTheme() + + return ( + + + + + + + OR + + + + + + Waiting for verification... + + + + ) +} + +function VerificationStatusStep({ + status, + onRetry, +}: { + status: VerificationStatuses + onRetry: () => void +}) { + const { palette } = useTheme() + + const icon = useMemo(() => { + switch (status) { + case VerificationStatuses.Verified: + return '✅' + case VerificationStatuses.UniquenessCheckFailed: + case VerificationStatuses.FailedVerification: + return '❌' + default: + return '⏳' + } + }, [status]) + + const title = useMemo(() => { + switch (status) { + case VerificationStatuses.Verified: + return 'Verified' + case VerificationStatuses.UniquenessCheckFailed: + return 'Uniqueness Check Failed' + case VerificationStatuses.FailedVerification: + return 'Failed Verification' + default: + return 'Waiting For Verification...' + } + }, [status]) + + const description = useMemo(() => { + switch (status) { + case VerificationStatuses.Verified: + return 'The proof is successfully verified' + case VerificationStatuses.UniquenessCheckFailed: + return 'The proof is valid, but the identity is not unique' + case VerificationStatuses.FailedVerification: + return 'The proof is invalid, verification failed' + default: + return 'The proof is being verified' + } + }, [status]) + + return ( + + + + {icon} + + {title} + + + {description} + + + + + + ) +} diff --git a/src/routes.tsx b/src/routes.tsx index 155ebfed..11a41aa5 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -10,6 +10,7 @@ import MainLayout from './layouts/MainLayout' export const AppRoutes = () => { const RewardsInvitationAlias = lazy(() => import('@/pages/RewardsInvitationAlias')) const DownloadApp = lazy(() => import('@/pages/DownloadApp')) + const ProofRequestsDemo = lazy(() => import('@/pages/ProofRequestsDemo')) const { isAuthorized } = useAuth() @@ -36,6 +37,10 @@ export const AppRoutes = () => { path: RoutePaths.DownloadApp, element: , }, + { + path: RoutePaths.ProofRequestsDemo, + element: , + }, { path: RoutePaths.Root, element: , diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 48274035..cdb63ed4 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -5,6 +5,7 @@ import { BuildMode, SupportedChains } from '@/types' interface ImportMetaEnv { VITE_MODE: BuildMode VITE_API_URL: string + VITE_VERIFICATOR_API_URL: string VITE_PORT: string VITE_APP_NAME: string VITE_APP_BUILD_VERSION: string