Skip to content
Merged
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
52 changes: 52 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,57 @@
"genericHint": "This should only take a moment.",
"iframeTitle": "Anchor deposit form",
"genericError": "Deposit failed"
},
"kycPage": {
"title": "KYC Verification",
"description": "Complete the verification process to comply with regulatory requirements and unlock full access to platform features.",
"whyKyc": "Why KYC?",
"reasonComply": "Comply with regulatory requirements",
"reasonLimits": "Increase transaction limits",
"reasonFeatures": "Access premium features",
"reasonSecurity": "Enhanced account security"
},
"kycForm": {
"formTitle": "KYC Submission Form",
"step": "Step",
"of": "of",
"steps": "Steps",
"completed": "completed",
"current": "current",
"upcoming": "upcoming",
"personalInfo": "Personal Information",
"addressInfo": "Address Information",
"documents": "Documents",
"review": "Review",
"firstName": "First Name",
"lastName": "Last Name",
"email": "Email",
"nationality": "Nationality",
"dateOfBirth": "Date of Birth",
"street": "Street",
"city": "City",
"state": "State",
"postalCode": "Postal Code",
"country": "Country",
"idType": "ID Type",
"selectIdType": "Select ID type",
"passport": "Passport",
"driversLicense": "Driver's License",
"nationalId": "National ID",
"idNumber": "ID Number",
"idFront": "ID Front",
"idBack": "ID Back",
"selfie": "Selfie",
"required": "Required",
"validationError": "Please fill in all required fields",
"back": "Back",
"next": "Next",
"submit": "Submit KYC",
"submitting": "Submitting...",
"submitError": "Submission failed",
"submitAnother": "Submit Another",
"successTitle": "KYC Submitted Successfully!",
"successDescription": "Your KYC verification has been submitted and is under review.",
"dash": "—"
}
}
52 changes: 52 additions & 0 deletions frontend/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,57 @@
"genericHint": "Esto solo deberia tomar un momento.",
"iframeTitle": "Formulario de deposito del anchor",
"genericError": "El deposito fallo"
},
"kycPage": {
"title": "Verificacion KYC",
"description": "Completa el proceso de verificacion para cumplir con los requisitos regulatorios y desbloquear el acceso completo a las funciones de la plataforma.",
"whyKyc": "Por que KYC?",
"reasonComply": "Cumplir con los requisitos regulatorios",
"reasonLimits": "Aumentar los limites de transaccion",
"reasonFeatures": "Acceder a funciones premium",
"reasonSecurity": "Seguridad mejorada de la cuenta"
},
"kycForm": {
"formTitle": "Formulario de envio KYC",
"step": "Paso",
"of": "de",
"steps": "Pasos",
"completed": "completado",
"current": "actual",
"upcoming": "proximo",
"personalInfo": "Informacion Personal",
"addressInfo": "Informacion de Direccion",
"documents": "Documentos",
"review": "Revisar",
"firstName": "Nombre",
"lastName": "Apellido",
"email": "Correo electronico",
"nationality": "Nacionalidad",
"dateOfBirth": "Fecha de nacimiento",
"street": "Calle",
"city": "Ciudad",
"state": "Estado",
"postalCode": "Codigo postal",
"country": "Pais",
"idType": "Tipo de ID",
"selectIdType": "Seleccionar tipo de ID",
"passport": "Pasaporte",
"driversLicense": "Licencia de conducir",
"nationalId": "ID Nacional",
"idNumber": "Numero de ID",
"idFront": "Frente del ID",
"idBack": "Reverso del ID",
"selfie": "Foto selfie",
"required": "Requerido",
"validationError": "Por favor completa todos los campos requeridos",
"back": "Atras",
"next": "Siguiente",
"submit": "Enviar KYC",
"submitting": "Enviando...",
"submitError": "Error al enviar",
"submitAnother": "Enviar otro",
"successTitle": "KYC enviado exitosamente!",
"successDescription": "Tu verificacion KYC ha sido enviada y esta en revision.",
"dash": "—"
}
}
52 changes: 52 additions & 0 deletions frontend/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,57 @@
"genericHint": "Isso deve levar apenas um momento.",
"iframeTitle": "Formulario de deposito do anchor",
"genericError": "Falha no deposito"
},
"kycPage": {
"title": "Verificacao KYC",
"description": "Complete o processo de verificacao para atender aos requisitos regulatorios e desbloquear o acesso completo aos recursos da plataforma.",
"whyKyc": "Por que KYC?",
"reasonComply": "Atender aos requisitos regulatorios",
"reasonLimits": "Aumentar os limites de transacao",
"reasonFeatures": "Acessar recursos premium",
"reasonSecurity": "Seguranca aprimorada da conta"
},
"kycForm": {
"formTitle": "Formulario de envio KYC",
"step": "Passo",
"of": "de",
"steps": "Passos",
"completed": "concluido",
"current": "atual",
"upcoming": "proximo",
"personalInfo": "Informacoes Pessoais",
"addressInfo": "Informacoes de Endereco",
"documents": "Documentos",
"review": "Revisar",
"firstName": "Nome",
"lastName": "Sobrenome",
"email": "E-mail",
"nationality": "Nacionalidade",
"dateOfBirth": "Data de nascimento",
"street": "Rua",
"city": "Cidade",
"state": "Estado",
"postalCode": "Codigo postal",
"country": "Pais",
"idType": "Tipo de ID",
"selectIdType": "Selecionar tipo de ID",
"passport": "Passaporte",
"driversLicense": "Carteira de motorista",
"nationalId": "ID Nacional",
"idNumber": "Numero do ID",
"idFront": "Frente do ID",
"idBack": "Verso do ID",
"selfie": "Foto selfie",
"required": "Obrigatorio",
"validationError": "Por favor preencha todos os campos obrigatorios",
"back": "Voltar",
"next": "Proximo",
"submit": "Enviar KYC",
"submitting": "Enviando...",
"submitError": "Falha no envio",
"submitAnother": "Enviar outro",
"successTitle": "KYC enviado com sucesso!",
"successDescription": "Sua verificacao KYC foi enviada e esta em revisao.",
"dash": "—"
}
}
6 changes: 6 additions & 0 deletions frontend/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
allowBuilds:
'@parcel/watcher': set this to true or false
'@sentry/cli': set this to true or false
'@swc/core': set this to true or false
chromedriver: set this to true or false
unrs-resolver: set this to true or false
28 changes: 2 additions & 26 deletions frontend/src/app/(authenticated)/kyc/page.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { Metadata } from "next";
import KycSubmissionForm from "@/components/KycSubmissionForm";
import KycPageContent from "@/components/KycPageContent";

export const metadata: Metadata = {
title: "KYC Verification | PLUTO",
description: "Complete your KYC verification to unlock full platform features",
};

export default function KycPage() {
return (
<div className="mx-auto max-w-2xl space-y-6 p-6">
<div className="space-y-2">
<h1 className="text-3xl font-bold text-white">KYC Verification</h1>
<p className="text-slate-400">
Complete the verification process to comply with regulatory requirements
and unlock full access to platform features.
</p>
</div>

<div className="rounded-2xl border border-white/10 bg-white/5 p-6 backdrop-blur">
<KycSubmissionForm />
</div>

<div className="rounded-xl border border-blue-500/30 bg-blue-500/10 p-4">
<h3 className="mb-2 font-semibold text-blue-400">Why KYC?</h3>
<ul className="space-y-1 text-sm text-slate-400">
<li>• Comply with regulatory requirements</li>
<li>• Increase transaction limits</li>
<li>• Access premium features</li>
<li>• Enhanced account security</li>
</ul>
</div>
</div>
);
return <KycPageContent />;
}
31 changes: 31 additions & 0 deletions frontend/src/components/KycPageContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useTranslations } from "next-intl";
import KycSubmissionForm from "@/components/KycSubmissionForm";

export default function KycPageContent() {
const t = useTranslations("kycPage");

return (
<div className="mx-auto max-w-2xl space-y-6 p-6">
<div className="space-y-2">
<h1 className="text-3xl font-bold text-white">{t("title")}</h1>
<p className="text-slate-400">{t("description")}</p>
</div>

<div className="rounded-2xl border border-white/10 bg-white/5 p-6 backdrop-blur">
<KycSubmissionForm />
</div>

<div className="rounded-xl border border-blue-500/30 bg-blue-500/10 p-4">
<h3 className="mb-2 font-semibold text-blue-400">{t("whyKyc")}</h3>
<ul className="space-y-1 text-sm text-slate-400">
<li>• {t("reasonComply")}</li>
<li>• {t("reasonLimits")}</li>
<li>• {t("reasonFeatures")}</li>
<li>• {t("reasonSecurity")}</li>
</ul>
</div>
</div>
);
}
32 changes: 23 additions & 9 deletions frontend/src/components/KycSubmissionForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ vi.mock("next-intl", () => ({
}));

// framer-motion: render plain HTML elements so jsdom doesn't choke on
// animation APIs. AnimatePresence just renders its children.
// animation APIs. AnimatePresence uses mode="wait" to ensure only the
// current step renders at a time.
vi.mock("framer-motion", async () => {
const React = await import("react");
const motion = new Proxy(
Expand All @@ -43,8 +44,14 @@ vi.mock("framer-motion", async () => {
);
return {
motion,
AnimatePresence: ({ children }: { children: React.ReactNode }) =>
React.createElement(React.Fragment, null, children),
AnimatePresence: ({ children, mode }: { children: React.ReactNode; mode?: string }) => {
// mode="wait" means only render the last child (current step)
if (mode === "wait") {
const childArray = React.Children.toArray(children);
return React.createElement(React.Fragment, null, childArray[childArray.length - 1]);
}
return React.createElement(React.Fragment, null, children);
},
type: {},
};
});
Expand Down Expand Up @@ -79,6 +86,13 @@ function navigateToStep(targetIndex: number) {
}
}

/** Navigate without re-filling personal step (use when already past step 1). */
function clickNext(times: number) {
for (let i = 0; i < times; i++) {
fireEvent.click(screen.getByText("next"));
}
}

// ─── Tests ────────────────────────────────────────────────────────────────────

describe("KycSubmissionForm", () => {
Expand Down Expand Up @@ -148,7 +162,7 @@ describe("KycSubmissionForm", () => {
navigateToStep(1);
expect(screen.getByText("2 of 4")).toBeInTheDocument();

navigateToStep(2);
clickNext(1);
expect(screen.getByText("3 of 4")).toBeInTheDocument();
});

Expand Down Expand Up @@ -296,7 +310,7 @@ describe("KycSubmissionForm", () => {
fireEvent.click(screen.getByText("submit"));

await waitFor(() => {
expect(screen.getByText("successTitle")).toBeInTheDocument();
expect(screen.getAllByText("successTitle").length).toBeGreaterThanOrEqual(1);
});
expect(mockToastSuccess).toHaveBeenCalled();
});
Expand Down Expand Up @@ -353,7 +367,7 @@ describe("KycSubmissionForm", () => {
fireEvent.click(screen.getByText("submit"));

await waitFor(() => {
expect(screen.getByText("successTitle")).toBeInTheDocument();
expect(screen.getAllByText("successTitle").length).toBeGreaterThanOrEqual(1);
});

fireEvent.click(screen.getByText("submitAnother"));
Expand Down Expand Up @@ -410,7 +424,7 @@ describe("KycSubmissionForm", () => {

it("marks the form container with role=region", () => {
render(React.createElement(KycSubmissionForm));
expect(screen.getByRole("region")).toBeInTheDocument();
expect(screen.getByRole("region", { name: "formTitle" })).toBeInTheDocument();
});

// ── Screen reader support ─────────────────────────────────────────────────
Expand Down Expand Up @@ -528,8 +542,8 @@ describe("KycSubmissionForm", () => {
fireEvent.click(screen.getByText("next")); // → address
fireEvent.click(screen.getByText("next")); // → documents
fireEvent.click(screen.getByText("next")); // → review
// city was not filled, so it renders the placeholder dash
const dashes = screen.getAllByText("");
// city was not filled, so it renders the placeholder dash (mock returns key)
const dashes = screen.getAllByText("dash");
expect(dashes.length).toBeGreaterThan(0);
});

Expand Down
Loading
Loading