feat: secure KYC storage, Google OAuth, terms gating

This commit is contained in:
arifal
2026-04-28 23:10:21 +07:00
parent 58da4608ac
commit 05d0929f7a
41 changed files with 3087 additions and 262 deletions
+16 -13
View File
@@ -2,17 +2,15 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { reviewVerificationAction } from "@/features/organizer/actions";
type Verification = {
id: string;
fullName: string;
/** NIK plaintext, sudah di-decrypt di server sebelum sampai ke komponen ini. */
nik: string;
birthDate: Date;
address: string;
ktpImageUrl: string;
selfieUrl: string;
bankName: string;
bankAccountNumber: string;
bankAccountName: string;
@@ -90,8 +88,14 @@ export function ReviewCard({ verification }: { verification: Verification }) {
</div>
<div className="mt-5 grid gap-4 sm:grid-cols-2">
<ImagePreview label="Foto KTP" url={verification.ktpImageUrl} />
<ImagePreview label="Selfie + KTP" url={verification.selfieUrl} />
<ImagePreview
label="Foto KTP"
src={`/api/files/kyc/${verification.id}/ktp`}
/>
<ImagePreview
label="Selfie + KTP"
src={`/api/files/kyc/${verification.id}/selfie`}
/>
</div>
{verification.status === "REJECTED" && verification.rejectionReason && (
@@ -196,26 +200,25 @@ function Field({
);
}
function ImagePreview({ label, url }: { label: string; url: string }) {
function ImagePreview({ label, src }: { label: string; src: string }) {
return (
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-neutral-500">
{label}
</p>
<a
href={url}
href={src}
target="_blank"
rel="noopener noreferrer"
className="block overflow-hidden rounded-xl border border-neutral-200 bg-neutral-100"
>
<div className="relative aspect-[4/3] w-full">
<Image
src={url}
{/* Secure endpoint sends Cache-Control: private,no-store. Use plain <img> to skip Next/Image optimizer. */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={src}
alt={label}
fill
unoptimized
className="object-cover"
sizes="(min-width: 640px) 50vw, 100vw"
className="h-full w-full object-cover"
/>
</div>
</a>