Skip to content

Commit eb56114

Browse files
committed
feat: eid wallet basic ui for verification
1 parent ea32829 commit eb56114

File tree

17 files changed

+505
-77
lines changed

17 files changed

+505
-77
lines changed

infrastructure/eid-wallet/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@
2727
"@tauri-apps/plugin-biometric": "^2.2.0",
2828
"@tauri-apps/plugin-opener": "^2",
2929
"@tauri-apps/plugin-store": "^2.2.0",
30+
"@veriff/incontext-sdk": "^2.4.0",
31+
"@veriff/js-sdk": "^1.5.1",
32+
"axios": "^1.6.7",
3033
"clsx": "^2.1.1",
34+
"dotenv": "^16.5.0",
3135
"flag-icons": "^7.3.2",
36+
"import": "^0.0.6",
37+
"svelte-loading-spinners": "^0.3.6",
3238
"tailwind-merge": "^3.0.2"
3339
},
3440
"devDependencies": {

infrastructure/eid-wallet/src-tauri/gen/apple/eid-wallet.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@
377377
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
378378
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
379379
CODE_SIGN_IDENTITY = "iPhone Developer";
380-
DEVELOPMENT_TEAM = 7F2T2WK6DR;
380+
DEVELOPMENT_TEAM = 3FS4B734X5;
381381
ENABLE_BITCODE = NO;
382382
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
383383
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -430,7 +430,7 @@
430430
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
431431
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
432432
CODE_SIGN_IDENTITY = "iPhone Developer";
433-
DEVELOPMENT_TEAM = 7F2T2WK6DR;
433+
DEVELOPMENT_TEAM = 3FS4B734X5;
434434
ENABLE_BITCODE = NO;
435435
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
436436
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;

infrastructure/eid-wallet/src/lib/global/controllers/user.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ export class UserController {
5454
* ```
5555
* @throws {Error} If the user state cannot be set in the store
5656
*/
57-
set user(user:
58-
| Promise<Record<string, string> | undefined>
59-
| Record<string, string>
60-
| undefined) {
57+
set user(
58+
user:
59+
| Promise<Record<string, string> | undefined>
60+
| Record<string, string>
61+
| undefined,
62+
) {
6163
if (user instanceof Promise) {
6264
user.then((resolvedUser) => {
6365
this.#store.set("user", resolvedUser);

infrastructure/eid-wallet/src/lib/ui/Button/ButtonAction.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const classes = $derived({
106106
</div>
107107
</button>
108108

109-
<!--
109+
<!--
110110
@component
111111
export default ButtonAction
112112
@description
Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,58 @@
11
<script lang="ts">
2-
import { SettingsNavigationBtn } from "$lib/fragments";
3-
import { runtime } from "$lib/global/runtime.svelte";
4-
import {
5-
Key01Icon,
6-
LanguageSquareIcon,
7-
Link02Icon,
8-
PinCodeIcon,
9-
Shield01Icon,
10-
} from "@hugeicons/core-free-icons";
2+
import { SettingsNavigationBtn } from "$lib/fragments";
3+
import { runtime } from "$lib/global/runtime.svelte";
4+
import {
5+
Key01Icon,
6+
LanguageSquareIcon,
7+
Link02Icon,
8+
PinCodeIcon,
9+
Shield01Icon,
10+
} from "@hugeicons/core-free-icons";
11+
import { ButtonAction } from "$lib/ui";
12+
import { getContext } from "svelte";
13+
import type { GlobalState } from "$lib/global";
14+
import { goto } from "$app/navigation";
1115
12-
$effect(() => {
13-
runtime.header.title = "Settings";
14-
});
16+
const globalState = getContext<() => GlobalState>("globalState")();
17+
18+
function nukeWallet() {
19+
globalState.userController.user = null;
20+
globalState.securityController.clearPin();
21+
goto("/onboarding");
22+
}
23+
24+
$effect(() => {
25+
runtime.header.title = "Settings";
26+
});
1527
</script>
1628

1729
<main>
1830
<!-- header part -->
19-
<SettingsNavigationBtn icon={LanguageSquareIcon} label="Language" href="/settings/language"/>
20-
<SettingsNavigationBtn icon={Link02Icon} label="History" href="/settings/history"/>
21-
<SettingsNavigationBtn icon={PinCodeIcon} label="Pin" href="/settings/pin"/>
22-
<SettingsNavigationBtn icon={Key01Icon} label="Keys" href="/settings/keys"/>
23-
<SettingsNavigationBtn icon={Shield01Icon} label="Privacy" href="/settings/privacy"/>
24-
</main>
31+
<SettingsNavigationBtn
32+
icon={LanguageSquareIcon}
33+
label="Language"
34+
href="/settings/language"
35+
/>
36+
<SettingsNavigationBtn
37+
icon={Link02Icon}
38+
label="History"
39+
href="/settings/history"
40+
/>
41+
<SettingsNavigationBtn
42+
icon={PinCodeIcon}
43+
label="Pin"
44+
href="/settings/pin"
45+
/>
46+
<SettingsNavigationBtn
47+
icon={Key01Icon}
48+
label="Keys"
49+
href="/settings/keys"
50+
/>
51+
<SettingsNavigationBtn
52+
icon={Shield01Icon}
53+
label="Privacy"
54+
href="/settings/privacy"
55+
/>
56+
57+
<ButtonAction callback={nukeWallet}>asdfasdf</ButtonAction>
58+
</main>
Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,62 @@
11
<script lang="ts">
2-
import { goto } from "$app/navigation";
3-
import { Hero } from "$lib/fragments";
4-
import { GlobalState } from "$lib/global";
5-
import { ButtonAction } from "$lib/ui";
6-
import { getContext, onMount } from "svelte";
2+
import { goto } from "$app/navigation";
3+
import { Hero } from "$lib/fragments";
4+
import { GlobalState } from "$lib/global";
5+
import { ButtonAction } from "$lib/ui";
6+
import { getContext, onMount } from "svelte";
7+
import Drawer from "$lib/ui/Drawer/Drawer.svelte";
8+
import axios from "axios";
9+
import { PUBLIC_PROVISIONER_URL } from "$env/static/public";
10+
import { verifStep } from "./store";
11+
import Passport from "./steps/passport.svelte";
12+
import Selfie from "./steps/selfie.svelte";
713
8-
let globalState: GlobalState | undefined = $state(undefined);
14+
let globalState: GlobalState | undefined = $state(undefined);
15+
let showVeriffModal = $state(false);
916
10-
let handleVerification: () => Promise<void>;
17+
async function handleVerification() {
18+
const { data } = await axios.post(
19+
new URL("/idv", PUBLIC_PROVISIONER_URL).toString(),
20+
);
21+
console.log(data);
22+
showVeriffModal = true;
23+
}
1124
12-
onMount(() => {
13-
globalState = getContext<() => GlobalState>("globalState")();
14-
// handle verification logic + set user data in the store
15-
handleVerification = async () => {
16-
if (!globalState) throw new Error("Global state is not defined");
17-
globalState.userController.user = {
18-
name: "John Doe",
19-
"Date of Birth": "01/01/2000",
20-
"ID submitted": "American Passport",
21-
"Passport Number": "1234567-US",
22-
};
23-
await goto("/register");
24-
};
25-
});
25+
onMount(() => {
26+
globalState = getContext<() => GlobalState>("globalState")();
27+
// handle verification logic + set user data in the store
28+
29+
// handleVerification = async () => {
30+
// if (!globalState) throw new Error("Global state is not defined");
31+
// globalState.userController.user = {
32+
// name: "John Doe",
33+
// "Date of Birth": "01/01/2000",
34+
// "ID submitted": "American Passport",
35+
// "Passport Number": "1234567-US",
36+
// };
37+
// await goto("/register");
38+
// };
39+
});
2640
</script>
2741

28-
<main class="h-screen pt-[3svh] px-[5vw] pb-[4.5svh] flex flex-col justify-between items-center">
42+
<main
43+
class="pt-[3svh] px-[5vw] pb-[4.5svh] flex flex-col justify-between items-center"
44+
>
2945
<section>
3046
<Hero
3147
title="Verify your account"
3248
subtitle="Get your passport ready. You’ll be directed to a site where you can verify your account in a swift and secure process"
3349
/>
34-
<img class="mx-auto mt-20" src="images/Passport.svg" alt="passport">
50+
<img class="mx-auto mt-20" src="images/Passport.svg" alt="passport" />
3551
</section>
36-
<ButtonAction class="w-full" callback={async() => {
37-
try {
38-
await handleVerification();
39-
} catch (error) {
40-
console.error("Verification failed:", error);
41-
// Consider adding user-facing error handling here
42-
}
43-
}}>I'm ready</ButtonAction>
52+
<ButtonAction class="w-full mt-10" callback={handleVerification}
53+
>I'm ready</ButtonAction
54+
>
55+
<Drawer bind:isPaneOpen={showVeriffModal}>
56+
{#if $verifStep === 0}
57+
<Passport></Passport>
58+
{:else if $verifStep === 1}
59+
<Selfie></Selfie>
60+
{/if}
61+
</Drawer>
4462
</main>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<script lang="ts">
2+
import { ButtonAction } from "$lib/ui";
3+
import { writable } from "svelte/store";
4+
import { permissionGranted, verifStep, DocFront } from "../store";
5+
import { onMount } from "svelte";
6+
7+
let error: string;
8+
9+
onMount(async () => {
10+
try {
11+
const stream = await navigator.mediaDevices.getUserMedia({
12+
video: true,
13+
});
14+
permissionGranted.set(true);
15+
stream.getTracks().forEach((track) => track.stop());
16+
} catch (err) {
17+
permissionGranted.set(false);
18+
console.error("Camera permission denied", err);
19+
error =
20+
"Camera permission denied. Please allow camera access and try again.";
21+
}
22+
});
23+
24+
let video: HTMLVideoElement;
25+
let canvas1: HTMLCanvasElement;
26+
let canvas2: HTMLCanvasElement;
27+
let image = 1;
28+
let image1Captured = writable(false);
29+
let loading = false;
30+
async function requestCameraPermission() {
31+
try {
32+
const stream = await navigator.mediaDevices.getUserMedia({
33+
video: { facingMode: "environment" },
34+
});
35+
video.srcObject = stream;
36+
video.play();
37+
permissionGranted.set(true);
38+
} catch (err) {
39+
permissionGranted.set(false);
40+
console.error("Camera permission denied", err);
41+
}
42+
}
43+
async function captureImage() {
44+
if (image === 1) {
45+
console.log("huh?");
46+
const context1 = canvas1.getContext("2d");
47+
if (context1) {
48+
context1.drawImage(video, 0, 0, 1920, 1080);
49+
canvas1.width = video.videoWidth;
50+
canvas1.height = video.videoHeight;
51+
context1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
52+
const dataUrl = canvas1.toDataURL("image/png");
53+
DocFront.set(dataUrl);
54+
loading = true;
55+
// await embedClient.post(`/verification/${id}/media`, {
56+
// img: dataUrl,
57+
// type: "document-front",
58+
// });
59+
loading = false;
60+
image1Captured.set(true);
61+
verifStep.set(1);
62+
}
63+
}
64+
}
65+
66+
onMount(() => {
67+
requestCameraPermission();
68+
});
69+
</script>
70+
71+
<div>
72+
{#if error}
73+
<div>{error}</div>
74+
{/if}
75+
<div class="flex flex-col h-[90vh]">
76+
<div class="flex flex-col items-center gap-1">
77+
<div class="mb-10">
78+
<h3>Present your Passport</h3>
79+
<p>
80+
Please place your passport's photo page within the rectangle
81+
and press the take photo button
82+
</p>
83+
</div>
84+
<div class="relative flex flex-col items-center justify-center">
85+
<!-- svelte-ignore a11y-media-has-caption -->
86+
<video
87+
bind:this={video}
88+
autoplay
89+
playsinline
90+
class=" aspect-[4/3] w-full rounded-lg object-cover"
91+
></video>
92+
<img
93+
src="/images/CameraFrame.svg"
94+
class="absolute left-[50%] top-[50%] w-[90%] translate-x-[-50%] translate-y-[-50%]"
95+
alt=""
96+
/>
97+
</div>
98+
<br />
99+
<canvas bind:this={canvas1} class="hidden"></canvas>
100+
<canvas bind:this={canvas2} class="hidden"></canvas>
101+
<ButtonAction
102+
disabled={loading}
103+
callback={captureImage}
104+
class="w-full"
105+
>{loading ? "Processing..." : "Take Photo"}</ButtonAction
106+
>
107+
</div>
108+
</div>
109+
</div>

0 commit comments

Comments
 (0)