From 1737458a3acd5d689db9394e3686010385a9488a Mon Sep 17 00:00:00 2001
From: Takis Kakalis <80459599+Takaros999@users.noreply.github.com>
Date: Thu, 28 May 2026 11:53:13 +0300
Subject: [PATCH] fix(react): add terminal widget errors
---
.../react/src/__tests__/widgets.test.tsx | 60 +++++++++++++++++++
.../src/components/States/ErrorState.tsx | 21 +++++++
js/packages/react/src/lang/translations/en.ts | 6 ++
js/packages/react/src/lang/translations/es.ts | 7 +++
js/packages/react/src/lang/translations/th.ts | 8 +++
js/packages/react/src/lang/types.ts | 4 ++
6 files changed, 106 insertions(+)
diff --git a/js/packages/react/src/__tests__/widgets.test.tsx b/js/packages/react/src/__tests__/widgets.test.tsx
index beee8736..0a156e70 100644
--- a/js/packages/react/src/__tests__/widgets.test.tsx
+++ b/js/packages/react/src/__tests__/widgets.test.tsx
@@ -257,6 +257,66 @@ describe("widgets", () => {
expect(flow.reset).not.toHaveBeenCalled();
});
+ it("request widget shows requirements state for identity attribute mismatches", async () => {
+ const flow = createFlow({
+ isError: true,
+ errorCode: IDKitErrorCodes.IdentityAttributesNotMatched,
+ });
+ useIDKitRequestMock.mockReturnValue(flow);
+
+ const onError = vi.fn();
+ const onOpenChange = vi.fn();
+
+ render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onError).toHaveBeenCalledWith(
+ IDKitErrorCodes.IdentityAttributesNotMatched,
+ );
+ });
+ expect(screen.getByText("Verification requirements not met")).toBeDefined();
+ expect(
+ screen.getByText(
+ "Your World ID doesn't meet the requirements for this verification.",
+ ),
+ ).toBeDefined();
+
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
+ expect(onOpenChange).toHaveBeenCalledWith(false);
+ expect(flow.reset).not.toHaveBeenCalled();
+ });
+
+ it("request widget shows terminal state for user presence failures", async () => {
+ const flow = createFlow({
+ isError: true,
+ errorCode: IDKitErrorCodes.UserPresenceFailed,
+ });
+ useIDKitRequestMock.mockReturnValue(flow);
+
+ const onError = vi.fn();
+ const onOpenChange = vi.fn();
+
+ render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onError).toHaveBeenCalledWith(IDKitErrorCodes.UserPresenceFailed);
+ });
+ expect(screen.getByText("Presence check failed")).toBeDefined();
+ expect(
+ screen.getByText(
+ "World App couldn't confirm your presence for this request.",
+ ),
+ ).toBeDefined();
+
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
+ expect(onOpenChange).toHaveBeenCalledWith(false);
+ expect(flow.reset).not.toHaveBeenCalled();
+ });
+
it("request widget waits for handleVerify to resolve before calling onSuccess", async () => {
const flow = createFlow({
isSuccess: true,
diff --git a/js/packages/react/src/components/States/ErrorState.tsx b/js/packages/react/src/components/States/ErrorState.tsx
index 1b1cb659..7d4236be 100644
--- a/js/packages/react/src/components/States/ErrorState.tsx
+++ b/js/packages/react/src/components/States/ErrorState.tsx
@@ -16,12 +16,15 @@ type ErrorVariant =
| "configuration_error"
| "connection"
| "host_verification"
+ | "identity_attributes_not_matched"
+ | "user_presence_failed"
| "generic";
const errorCodeVariants: Partial> = {
[IDKitErrorCodes.UserRejected]: "cancelled",
[IDKitErrorCodes.VerificationRejected]: "cancelled",
[IDKitErrorCodes.Cancelled]: "cancelled",
+ [IDKitErrorCodes.UserPresenceFailed]: "user_presence_failed",
[IDKitErrorCodes.ConnectionFailed]: "connection",
[IDKitErrorCodes.FailedByHostApp]: "host_verification",
[IDKitErrorCodes.InvalidRpSignature]: "configuration_error",
@@ -33,6 +36,8 @@ const errorCodeVariants: Partial> = {
[IDKitErrorCodes.TimestampTooFarInFuture]: "configuration_error",
[IDKitErrorCodes.InvalidTimestamp]: "configuration_error",
[IDKitErrorCodes.RpSignatureExpired]: "configuration_error",
+ [IDKitErrorCodes.IdentityAttributesNotMatched]:
+ "identity_attributes_not_matched",
[IDKitErrorCodes.InvalidRpIdFormat]: "configuration_error",
};
@@ -74,6 +79,22 @@ const variantConfig = {
actionLabel: "Try Again" as const,
action: "retry" as const,
},
+ identity_attributes_not_matched: {
+ title: "Verification requirements not met" as const,
+ message:
+ "Your World ID doesn't meet the requirements for this verification." as const,
+ Icon: WarningIcon,
+ actionLabel: "Close" as const,
+ action: "close" as const,
+ },
+ user_presence_failed: {
+ title: "Presence check failed" as const,
+ message:
+ "World App couldn't confirm your presence for this request." as const,
+ Icon: WarningIcon,
+ actionLabel: "Close" as const,
+ action: "close" as const,
+ },
generic: {
title: "Something went wrong" as const,
message: "We couldn't complete your request. Please try again." as const,
diff --git a/js/packages/react/src/lang/translations/en.ts b/js/packages/react/src/lang/translations/en.ts
index 415840ad..db330dbf 100644
--- a/js/packages/react/src/lang/translations/en.ts
+++ b/js/packages/react/src/lang/translations/en.ts
@@ -19,6 +19,12 @@ export const en: TranslationStrings = {
"Verification declined": "Verification declined",
"Failed to verify your credential proof. Please contact the website owner.":
"Failed to verify your credential proof. Please contact the website owner.",
+ "Verification requirements not met": "Verification requirements not met",
+ "Your World ID doesn't meet the requirements for this verification.":
+ "Your World ID doesn't meet the requirements for this verification.",
+ "Presence check failed": "Presence check failed",
+ "World App couldn't confirm your presence for this request.":
+ "World App couldn't confirm your presence for this request.",
"We couldn't complete your request. Please try again.":
"We couldn't complete your request. Please try again.",
"Try Again": "Try Again",
diff --git a/js/packages/react/src/lang/translations/es.ts b/js/packages/react/src/lang/translations/es.ts
index 36d33283..772fd2e6 100644
--- a/js/packages/react/src/lang/translations/es.ts
+++ b/js/packages/react/src/lang/translations/es.ts
@@ -19,6 +19,13 @@ export const es: TranslationStrings = {
"Verification declined": "Verificaci\u00f3n rechazada",
"Failed to verify your credential proof. Please contact the website owner.":
"No se pudo verificar tu prueba de credencial. Por favor contacta al propietario del sitio web.",
+ "Verification requirements not met":
+ "No se cumplen los requisitos de verificacion",
+ "Your World ID doesn't meet the requirements for this verification.":
+ "Tu World ID no cumple los requisitos para esta verificacion.",
+ "Presence check failed": "Fallo la comprobacion de presencia",
+ "World App couldn't confirm your presence for this request.":
+ "World App no pudo confirmar tu presencia para esta solicitud.",
"We couldn't complete your request. Please try again.":
"No pudimos completar tu solicitud. Por favor intenta de nuevo.",
"Try Again": "Intentar de nuevo",
diff --git a/js/packages/react/src/lang/translations/th.ts b/js/packages/react/src/lang/translations/th.ts
index 31fa0dc6..cbf376e7 100644
--- a/js/packages/react/src/lang/translations/th.ts
+++ b/js/packages/react/src/lang/translations/th.ts
@@ -26,6 +26,14 @@ export const th: TranslationStrings = {
"\u0e01\u0e32\u0e23\u0e22\u0e37\u0e19\u0e22\u0e31\u0e19\u0e16\u0e39\u0e01\u0e1b\u0e0f\u0e34\u0e40\u0e2a\u0e18",
"Failed to verify your credential proof. Please contact the website owner.":
"\u0e44\u0e21\u0e48\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e22\u0e37\u0e19\u0e22\u0e31\u0e19\u0e2b\u0e25\u0e31\u0e01\u0e10\u0e32\u0e19\u0e02\u0e2d\u0e07 Credential \u0e44\u0e14\u0e49 \u0e42\u0e1b\u0e23\u0e14\u0e15\u0e34\u0e14\u0e15\u0e48\u0e2d\u0e40\u0e08\u0e49\u0e32\u0e02\u0e2d\u0e07\u0e40\u0e27\u0e47\u0e1a\u0e44\u0e0b\u0e15\u0e4c",
+ "Verification requirements not met":
+ "\u0e44\u0e21\u0e48\u0e40\u0e1b\u0e47\u0e19\u0e44\u0e1b\u0e15\u0e32\u0e21\u0e02\u0e49\u0e2d\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e01\u0e32\u0e23\u0e22\u0e37\u0e19\u0e22\u0e31\u0e19",
+ "Your World ID doesn't meet the requirements for this verification.":
+ "World ID \u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e15\u0e23\u0e07\u0e15\u0e32\u0e21\u0e02\u0e49\u0e2d\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e01\u0e32\u0e23\u0e22\u0e37\u0e19\u0e22\u0e31\u0e19\u0e19\u0e35\u0e49",
+ "Presence check failed":
+ "\u0e01\u0e32\u0e23\u0e15\u0e23\u0e27\u0e08\u0e2a\u0e2d\u0e1a\u0e01\u0e32\u0e23\u0e21\u0e35\u0e2d\u0e22\u0e39\u0e48\u0e25\u0e49\u0e21\u0e40\u0e2b\u0e25\u0e27",
+ "World App couldn't confirm your presence for this request.":
+ "World App \u0e44\u0e21\u0e48\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e22\u0e37\u0e19\u0e22\u0e31\u0e19\u0e01\u0e32\u0e23\u0e21\u0e35\u0e2d\u0e22\u0e39\u0e48\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e04\u0e33\u0e02\u0e2d\u0e19\u0e35\u0e49\u0e44\u0e14\u0e49",
"We couldn't complete your request. Please try again.":
"\u0e40\u0e23\u0e32\u0e44\u0e21\u0e48\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e14\u0e33\u0e40\u0e19\u0e34\u0e19\u0e01\u0e32\u0e23\u0e15\u0e32\u0e21\u0e04\u0e33\u0e02\u0e2d\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49 \u0e01\u0e23\u0e38\u0e13\u0e32\u0e25\u0e2d\u0e07\u0e2d\u0e35\u0e01\u0e04\u0e23\u0e31\u0e49\u0e07",
"Try Again":
diff --git a/js/packages/react/src/lang/types.ts b/js/packages/react/src/lang/types.ts
index 080fd5b5..78bc6bc6 100644
--- a/js/packages/react/src/lang/types.ts
+++ b/js/packages/react/src/lang/types.ts
@@ -18,6 +18,10 @@ export interface TranslationStrings {
"Please check your connection and try again.": string;
"Verification declined": string;
"Failed to verify your credential proof. Please contact the website owner.": string;
+ "Verification requirements not met": string;
+ "Your World ID doesn't meet the requirements for this verification.": string;
+ "Presence check failed": string;
+ "World App couldn't confirm your presence for this request.": string;
"We couldn't complete your request. Please try again.": string;
"Try Again": string;
Close: string;