diff --git a/apps/easypid/src/features/share/components/RequestedAttributesSection.tsx b/apps/easypid/src/features/share/components/RequestedAttributesSection.tsx
index b32067fd..d914d848 100644
--- a/apps/easypid/src/features/share/components/RequestedAttributesSection.tsx
+++ b/apps/easypid/src/features/share/components/RequestedAttributesSection.tsx
@@ -4,29 +4,70 @@ import {
type FormattedSubmissionEntrySatisfied,
getDisclosedAttributeNamesForDisplay,
getUnsatisfiedAttributePathsForDisplay,
+ useCredentialsForDisplay,
} from '@package/agent'
import { CardWithAttributes } from '@package/app'
-import { Heading, Paragraph, YStack } from '@package/ui'
+import { Heading, HeroIcons, Paragraph, XStack, YStack } from '@package/ui'
+import { useEffect, useMemo, useState } from 'react'
export type RequestedAttributesSectionProps = {
submission: FormattedSubmission
}
+const copy = {
+ satisfied: {
+ title: 'REQUESTED CARDS',
+ description: 'The following cards will be shared.',
+ variant: 'default',
+ },
+ unsatisfied: {
+ title: 'CARDS UNAVAILABLE',
+ description: "You don't have the requested card(s).",
+ variant: '$danger-500',
+ },
+ invalid: {
+ title: 'ATTRIBUTES UNAVAILABLE',
+ description: 'The verifier requested attributes that are not present in your card(s).',
+ variant: '$danger-500',
+ },
+}
+
export function RequestedAttributesSection({ submission }: RequestedAttributesSectionProps) {
const satisfiedEntries = submission.entries.filter((e): e is FormattedSubmissionEntrySatisfied => e.isSatisfied)
const unsatisfiedEntries = submission.entries.filter((e): e is FormattedSubmissionEntryNotSatisfied => !e.isSatisfied)
+ const { credentials } = useCredentialsForDisplay()
+ const [state, setState] = useState<'satisfied' | 'unsatisfied' | 'invalid'>(
+ satisfiedEntries.length === 0 ? 'satisfied' : 'unsatisfied'
+ )
+
+ useEffect(() => {
+ const hasInvalidCredential = unsatisfiedEntries.some((entry) =>
+ credentials.find((c) => c.metadata.type === `https://${entry.name}`)
+ )
+
+ if (hasInvalidCredential) setState('invalid')
+ }, [credentials, unsatisfiedEntries])
+
+ const formatUnsatisfiedEntries = useMemo(() => {
+ return unsatisfiedEntries.map((entry) => {
+ const credential = credentials.find((c) => c.metadata.type === `https://${entry.name}`)
+ return {
+ ...entry,
+ credential,
+ }
+ })
+ }, [credentials, unsatisfiedEntries])
return (
- {satisfiedEntries.length > 0 ? 'REQUESTED CARDS' : 'UNAVAILABLE CARDS'}
-
- {unsatisfiedEntries.length === 0
- ? 'The following cards will be shared.'
- : satisfiedEntries.length === 0
- ? `You don't have the requested card(s).`
- : `You don't have all of the requested cards.`}
-
+
+ {state !== 'satisfied' && }
+
+ {copy[state].title}
+
+
+ {copy[state].description}
{/* We always take the first one for now (no selection) */}
{satisfiedEntries.map(({ credentials: [credential], ...entry }) => {
@@ -54,26 +95,47 @@ export function RequestedAttributesSection({ submission }: RequestedAttributesSe
/>
)
})}
- {unsatisfiedEntries.length > 0 && (
+ {formatUnsatisfiedEntries.length > 0 && (
<>
- {satisfiedEntries.length !== 0 && (
-
- UNAVAILABLE CARDS
-
- )}
- {unsatisfiedEntries.map((entry) => (
-
- ))}
+ {formatUnsatisfiedEntries.map(({ credential, ...entry }) => {
+ const availableAttributes = Object.keys(credential?.attributes ?? {})
+ const requestedAttributes = getUnsatisfiedAttributePathsForDisplay(entry.requestedAttributePaths)
+ const missingAttributes = requestedAttributes.filter((attr) => !availableAttributes.includes(attr))
+ const attributeValuesThatCouldBeDisclosed = requestedAttributes.filter((attr) =>
+ availableAttributes.includes(attr)
+ )
+
+ // Attributes with their values that could be found in the credential
+ const attributesWithValuesThatCouldBeDisclosed = attributeValuesThatCouldBeDisclosed.reduce<
+ Record
+ >(
+ (acc, attr) => ({
+ ...acc,
+ [attr]: credential?.attributes[attr],
+ }),
+ {}
+ )
+
+ // Add missing attributes to the disclosed payload without values
+ const disclosedPayloadWithMissingAttributes = {
+ ...attributesWithValuesThatCouldBeDisclosed,
+ ...Object.fromEntries(missingAttributes.map((attr) => [attr, 'value-not-found'])),
+ }
+
+ return (
+
+ )
+ })}
>
)}
diff --git a/apps/easypid/src/features/share/slides/ShareCredentialsSlide.tsx b/apps/easypid/src/features/share/slides/ShareCredentialsSlide.tsx
index 5db1436c..26a30cae 100644
--- a/apps/easypid/src/features/share/slides/ShareCredentialsSlide.tsx
+++ b/apps/easypid/src/features/share/slides/ShareCredentialsSlide.tsx
@@ -2,7 +2,7 @@ import type { OverAskingResponse } from '@easypid/use-cases/OverAskingApi'
import type { DisplayImage, FormattedSubmission } from '@package/agent'
import { DualResponseButtons, useScrollViewPosition } from '@package/app'
import { useWizard } from '@package/app'
-import { Button, Heading, HeroIcons, MessageBox, Paragraph, ScrollView, YStack } from '@package/ui'
+import { Button, Heading, HeroIcons, MessageBox, ScrollView, YStack } from '@package/ui'
import { useState } from 'react'
import { Spacer } from 'tamagui'
import { RequestPurposeSection } from '../components/RequestPurposeSection'
@@ -104,12 +104,7 @@ export const ShareCredentialsSlide = ({
isLoading={isProcessing}
/>
) : (
-
-
- You don't have the required cards
-
- Close
-
+ Close
)}
diff --git a/packages/app/src/components/CardWithAttributes.tsx b/packages/app/src/components/CardWithAttributes.tsx
index 8d23f73b..f42caa0b 100644
--- a/packages/app/src/components/CardWithAttributes.tsx
+++ b/packages/app/src/components/CardWithAttributes.tsx
@@ -16,7 +16,7 @@ import { useRouter } from 'expo-router'
import { useMemo } from 'react'
import { BlurBadge } from './BlurBadge'
-interface CardWithAttributesProps {
+export interface CardWithAttributesProps {
id?: string
name: string
backgroundColor?: string
diff --git a/packages/app/src/components/CredentialAttributes.tsx b/packages/app/src/components/CredentialAttributes.tsx
index 080e4f49..30ad8513 100644
--- a/packages/app/src/components/CredentialAttributes.tsx
+++ b/packages/app/src/components/CredentialAttributes.tsx
@@ -229,6 +229,8 @@ const PrimitiveArrayRow = ({ name, value }: { name: string; value: (string | num
}
const ValueRow = ({ name, value }: { name: string; value: string }) => {
+ const isInvalid = value === 'value-not-found'
+
return (
{
{name}
- {value}
+ {isInvalid ? 'Not found' : value}
)
}