id-document.tsx (6088B)
1 import { MRZInfo } from "#core/application/id_document/mrzscan.ts"; 2 import { PhotoCaptureInput } from "#http/islands/photo_capture_input.tsx"; 3 import { AppState } from "#http/routes/_middleware.ts"; 4 import { Handlers, PageProps } from "$fresh/src/server/types.ts"; 5 import * as V from "$valita"; 6 7 type Props = { 8 side: "doc-front" | "doc-back" | "face-left" | "face-front" | "face-right"; 9 next: URL | null; 10 back: URL | null; 11 info: MRZInfo | null; 12 }; 13 14 const NEXT = { 15 "doc-front": "doc-back", 16 "doc-back": "face-left", 17 "face-left": "face-front", 18 "face-front": "face-right", 19 "face-right": false, 20 }; 21 22 export const handler: Handlers<Props, AppState<"/register/id-document">> = { 23 async GET(_req, ctx) { 24 const { app, forms, formContext } = ctx.state; 25 const { customerInfo } = app; 26 if (formContext === null) { 27 return forms.redirect({ 28 form: "/connect", 29 context: { 30 back: { 31 form: "/register/id-document", 32 context: { side: "doc-front", back: "/" }, 33 }, 34 }, 35 }); 36 } 37 const { side, back } = formContext; 38 if (forms.session === null) { 39 return forms.redirect({ 40 form: "/connect", 41 context: { 42 back: { 43 form: "/register/id-document", 44 context: { side: "doc-front", back }, 45 }, 46 }, 47 }); 48 } 49 50 const result = await customerInfo.execute({ uuid: forms.session.uuid }); 51 if (result.idDocumentRegistered) { 52 return forms.redirect(back); 53 } 54 55 return ctx.render({ 56 side, 57 next: forms.link({ 58 form: "/register/id-document", 59 context: { side: NEXT[side] as never, back }, 60 }), 61 back: null, 62 info: null, 63 }); 64 }, 65 66 async POST(req, ctx) { 67 const { app, forms, formContext } = ctx.state; 68 const { idDocumentCapture } = app; 69 70 if (formContext === null) { 71 return forms.redirect({ form: "/connect", context: { back: "/" } }); 72 } 73 74 const { side, back } = formContext; 75 if (forms.session === null) { 76 return forms.redirect({ 77 form: "/connect", 78 context: { 79 back: { 80 form: "/register/id-document", 81 context: { side: "doc-front", back }, 82 }, 83 }, 84 }); 85 } 86 87 const { picture } = await forms.inputs( 88 req, 89 V.object({ picture: V.string() }), 90 ); 91 92 const result = await idDocumentCapture.execute({ 93 uuid: forms.session.uuid, 94 side, 95 picture, 96 }); 97 98 if (result.status === "scanned") { 99 return ctx.render({ 100 side, 101 next: forms.link({ 102 form: "/register/id-document", 103 context: { side: "face-left" as never, back }, 104 }), 105 back: forms.link({ 106 form: "/register/id-document", 107 context: { side: "doc-front" as never, back }, 108 }), 109 info: result, 110 }); 111 } 112 113 if (result.status === "scan-failure") { 114 return forms.redirect({ 115 form: "/connect", 116 context: { 117 back: { 118 form: "/register/id-document", 119 context: { side: "doc-back", back }, 120 }, 121 }, 122 }); 123 } 124 125 126 if (result.status === "invalid") { 127 return forms.redirect({ 128 form: "/connect", 129 context: { 130 back: { 131 form: "/register/id-document", 132 context: { side: "doc-front", back }, 133 }, 134 }, 135 }); 136 } 137 138 if (result.status === "captured" && NEXT[side] !== false) { 139 return forms.redirect({ 140 form: "/register/id-document", 141 context: { side: NEXT[side] as never, back }, 142 }); 143 } 144 145 return forms.redirect(back); 146 }, 147 }; 148 149 const LABELS = { 150 "doc-front": "Take photo of ID document front side", 151 "doc-back": "Take photo of ID document back side", 152 "face-left": "Take photo of you facing left", 153 "face-front": "Take photo of you facing front", 154 "face-right": "Take photo of you facing right", 155 }; 156 157 export default function RegisterPages({ data }: PageProps<Props>) { 158 if (data.info !== null) { 159 return ( 160 <article> 161 <header style="text-align: center;"> 162 <b>ID Information</b> 163 </header> 164 <div> 165 <table> 166 <tr> 167 <th> 168 <b>First name</b> 169 </th> 170 <td>{data.info.firstName ? data.info.firstName : "—"}</td> 171 </tr> 172 <tr> 173 <th> 174 <b>Last name</b> 175 </th> 176 <td>{data.info.lastName ? data.info.lastName : "—"}</td> 177 </tr> 178 <tr> 179 <th> 180 <b>Sex</b> 181 </th> 182 <td>{data.info.sex ? data.info.sex : "—"}</td> 183 </tr> 184 <tr> 185 <th> 186 <b>Birth date</b> 187 </th> 188 <td> 189 {data.info.birthDate 190 ? data.info.birthDate.toLocaleDateString("en", { 191 year: "numeric", 192 month: "long", 193 day: "numeric", 194 }) 195 : "—"} 196 </td> 197 </tr> 198 <tr> 199 <th> 200 <b>Nationality</b> 201 </th> 202 <td>{data.info.nationality ? data.info.nationality : "—"}</td> 203 </tr> 204 <tr> 205 <th> 206 <b>Country</b> 207 </th> 208 <td>{data.info.country ? data.info.country : "—"}</td> 209 </tr> 210 </table> 211 <div role="group"> 212 <a href={data.back!.href} role="button" class="secondary">Back</a> 213 <a href={data.next!.href} role="button">Confirm</a> 214 </div> 215 </div> 216 </article> 217 ); 218 } 219 220 return ( 221 <article> 222 <header style="text-align: center;"> 223 <b>ID Document</b> 224 </header> 225 <form method="POST"> 226 <PhotoCaptureInput 227 camera={data.side.startsWith("face") ? "user" : "environment"} 228 > 229 {LABELS[data.side]} 230 </PhotoCaptureInput> 231 </form> 232 </article> 233 ); 234 }