diff --git a/TODO.md b/TODO.md index 6776ecd..e157289 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ ## Progress - [x] Repo reconnaissance: reviewed `useAccessCheck`, `access-check` screen, `guild detail` screen, and core access-related hooks. -- [ ] Locate where backend roles/eligibility requirements are computed for access-check. -- [ ] Implement resolver module + integration points. -- [ ] Add tests. -- [ ] Add docs. +- [x] Locate where backend roles/eligibility requirements are computed for access-check. +- [x] Implement resolver module + integration points. +- [x] Add tests (`roleEligibilityResolver.test.ts`, `useMultiChainRoleEligibility.test.ts`, `accessCheckScreen.test.tsx`). +- [x] Add docs (`docs/multi-chain-role-eligibility.md`). diff --git a/app/_layout.tsx b/app/_layout.tsx index d75cf78..c7cd3c0 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -66,6 +66,22 @@ export default function RootLayout() { > + + + + + + + + + + + = }; const statusClassName: Record = { + resolved: "bg-success/10 text-success border-success/30 dark:bg-green-900/30 dark:text-green-400 dark:border-green-600/50", + "timed-out": "bg-amber-100 text-amber-700 border-amber-300 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-600/50", + error: "bg-error/10 text-error border-error/30 dark:bg-red-900/30 dark:text-red-400 dark:border-red-600/50", resolved: "bg-success/10 text-success border-success/30 dark:bg-green-900/30 dark:text-green-400 dark:border-green-600/50", "timed-out": @@ -74,6 +77,7 @@ function PerChainEligibilityList({ {roleEligibilityError ? ( + ) : null} + {perChainRoleEligibility.map((chain) => ( + + + Chain {chain.chainId} + + {statusCopy[chain.status]} + + + {chain.resolvedRoles && chain.resolvedRoles.length > 0 ? ( + + Roles: {chain.resolvedRoles.join(", ")} + + ) : null} + {chain.errorMessage ? ( + {chain.errorMessage} + ) : null} + + ))} {perChainRoleEligibility.map((chain) => { const isRetrying = retryingChainIds.includes(chain.chainId); const canRetry = chain.chainId > 0 && chain.status !== "resolved" && onRetryChain; @@ -409,6 +437,11 @@ export default function AccessCheck() { accessibilityHint="Enter the guild identifier" testID="access-check-guild-id-input" /> + {guildIdError && {guildIdError}} + + + + Resource ID {guildIdError && ( {guildIdError} )} @@ -427,6 +460,7 @@ export default function AccessCheck() { accessibilityHint="Enter the resource identifier" testID="access-check-resource-id-input" /> + {resourceIdError && {resourceIdError}} {resourceIdError && ( {resourceIdError} )} @@ -468,6 +502,7 @@ export default function AccessCheck() { accessibilityLabel="Wallet address mismatch warning. This QR payload uses a different wallet address from your connected wallet." > Wallet address mismatch + {walletMismatchWarning} {walletMismatchWarning} @@ -512,6 +547,11 @@ export default function AccessCheck() { Guild ID + {scannedPayload.guildId} + + + Resource ID + {scannedPayload.resourceId} {scannedPayload.guildId} @@ -556,6 +596,7 @@ export default function AccessCheck() { }} > {countdown.isExpired && scannedPayload?.expiresAt && ( + { export default function AccessScanner() { const router = useRouter(); const [permission, requestPermission] = useCameraPermissions(); + const [scanError, setScanError] = useState<{ message: string; isUntrusted: boolean } | null>(null); const [scanError, setScanError] = useState<{ message: string; isUntrusted: boolean } | null>( null, ); @@ -140,6 +144,19 @@ export default function AccessScanner() { setScanError(null); AccessibilityInfo.announceForAccessibility("Processing access QR code."); + const result = await verifyAndParseAccessQrPayload(data); + + try { + AccessibilityInfo.announceForAccessibility("Processing access QR code."); + await verifyAndParseAccessQrPayload(data); + setVerificationSuccess(true); + AccessibilityInfo.announceForAccessibility("Signature verified. Opening access check."); + + setTimeout(() => { + setVerificationSuccess(false); + router.replace({ pathname: "/access-check", params: { qrPayload: data } }); + }, 1500); + return; try { AccessibilityInfo.announceForAccessibility("Processing access QR code."); const result = await verifyAndParseAccessQrPayload(data); @@ -194,6 +211,7 @@ export default function AccessScanner() { if (error instanceof QrSignatureError) { errorMessage = describeQrSignatureError(error.code); + isUntrusted = true; isUntrusted = !isRecoverableError(error); } else if (error instanceof QrPayloadError) { if ( @@ -208,6 +226,8 @@ export default function AccessScanner() { } else { errorMessage = error.message; } + } else if (error instanceof Error) { + errorMessage = error.message; errorMessage = describeQrPayloadError(error.code); isUntrusted = !isRecoverableError(error); } @@ -244,6 +264,7 @@ export default function AccessScanner() { + Checking camera permission... Checking camera permission... @@ -266,6 +287,9 @@ export default function AccessScanner() { + Camera access needed + + GuildPass needs camera permission to scan access check QR codes. ) : ( + <> + + Camera permission was denied. Enable camera access in your device settings to scan + QR codes. + + + Camera permission was denied. Open Settings to enable camera access for GuildPass. + +