diff --git a/src/components/EntityGrid.vue b/src/components/EntityGrid.vue index ff995652..ec5fe236 100644 --- a/src/components/EntityGrid.vue +++ b/src/components/EntityGrid.vue @@ -22,7 +22,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */ 0 && this.showAllRoute !== ""; + return ( + !this.hideShowAll && this.entities.length > 0 && this.showAllRoute !== "" + ); } /** @@ -271,10 +278,17 @@ export default class EntityGrid extends Vue { get unnamedEntityData(): { did: string; name: string } { return { did: "", - name: "Unnamed", + name: UNNAMED_ENTITY_NAME, }; } + /** + * Get the unnamed entity name constant + */ + get unnamedEntityName(): string { + return UNNAMED_ENTITY_NAME; + } + /** * Check if a person DID is conflicted */ @@ -304,16 +318,13 @@ export default class EntityGrid extends Vue { /** * Handle special entity selection from SpecialEntityCard + * Treat "You" and "Unnamed" as person entities */ - handleEntitySelected(event: { - type: string; - entityType: string; - data: { did?: string; name: string }; - }): void { + handleEntitySelected(event: { data: { did?: string; name: string } }): void { + // Convert special entities to person entities since they represent people this.emitEntitySelected({ - type: "special", - entityType: event.entityType, - data: event.data, + type: "person", + data: event.data as Contact, }); } @@ -321,13 +332,11 @@ export default class EntityGrid extends Vue { @Emit("entity-selected") emitEntitySelected(data: { - type: "person" | "project" | "special"; - entityType?: string; - data: Contact | PlanData | { did?: string; name: string }; + type: "person" | "project"; + data: Contact | PlanData; }): { - type: "person" | "project" | "special"; - entityType?: string; - data: Contact | PlanData | { did?: string; name: string }; + type: "person" | "project"; + data: Contact | PlanData; } { return data; } diff --git a/src/components/EntitySelectionStep.vue b/src/components/EntitySelectionStep.vue index 56426462..2fb6bcac 100644 --- a/src/components/EntitySelectionStep.vue +++ b/src/components/EntitySelectionStep.vue @@ -27,6 +27,7 @@ Matthew Raymer */ :show-all-query-params="showAllQueryParams" :notify="notify" :conflict-context="conflictContext" + :hide-show-all="hideShowAll" @entity-selected="handleEntitySelected" /> @@ -55,9 +56,8 @@ interface EntityData { * Entity selection event data structure */ interface EntitySelectionEvent { - type: "person" | "project" | "special"; - entityType?: string; - data: Contact | PlanData | EntityData; + type: "person" | "project"; + data: Contact | PlanData; } /** @@ -154,6 +154,10 @@ export default class EntitySelectionStep extends Vue { @Prop() notify?: (notification: NotificationIface, timeout?: number) => void; + /** Whether to hide the "Show All" navigation */ + @Prop({ default: false }) + hideShowAll!: boolean; + /** * CSS classes for the cancel button */ diff --git a/src/components/EntitySummaryButton.vue b/src/components/EntitySummaryButton.vue index 50f25078..80890eff 100644 --- a/src/components/EntitySummaryButton.vue +++ b/src/components/EntitySummaryButton.vue @@ -42,8 +42,8 @@ computed CSS properties * * @author Matthew Raymer */

{{ label }}

-

- {{ entity?.name || "Unnamed" }} +

+ {{ displayName }}

@@ -62,6 +62,7 @@ import { Component, Prop, Vue } from "vue-facing-decorator"; import EntityIcon from "./EntityIcon.vue"; import ProjectIcon from "./ProjectIcon.vue"; import { Contact } from "../db/tables/contacts"; +import { UNNAMED_ENTITY_NAME } from "@/constants/entities"; /** * Entity interface for both person and project entities @@ -138,6 +139,38 @@ export default class EntitySummaryButton extends Vue { return this.editable ? "text-blue-500" : "text-slate-400"; } + /** + * Computed CSS classes for the entity name + */ + get nameClasses(): string { + const baseClasses = "font-semibold truncate"; + + // Add italic styling for special "Unnamed" or entities without set names + if (!this.entity?.name || this.entity?.did === "") { + return `${baseClasses} italic text-slate-500`; + } + + return baseClasses; + } + + /** + * Computed display name for the entity + */ + get displayName(): string { + // If the entity has a set name, use that name + if (this.entity?.name) { + return this.entity.name; + } + + // If the entity is the special "Unnamed", use "Unnamed" + if (this.entity?.did === "") { + return UNNAMED_ENTITY_NAME; + } + + // If the entity does not have a set name, but is not the special "Unnamed", use their DID + return this.entity?.did; + } + /** * Handle click event - only call function prop if editable * Allows parent to control edit behavior and validation diff --git a/src/components/GiftedDialog.vue b/src/components/GiftedDialog.vue index fc393f53..d91e68b3 100644 --- a/src/components/GiftedDialog.vue +++ b/src/components/GiftedDialog.vue @@ -29,6 +29,7 @@ :unit-code="unitCode" :offer-id="offerId" :notify="$notify" + :hide-show-all="hideShowAll" @entity-selected="handleEntitySelected" @cancel="cancel" /> @@ -87,6 +88,7 @@ import { NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER, NOTIFY_GIFTED_DETAILS_RECORDING_GIVE, } from "@/constants/notifications"; +import { UNNAMED_ENTITY_NAME } from "@/constants/entities"; @Component({ components: { @@ -114,6 +116,7 @@ export default class GiftedDialog extends Vue { @Prop() fromProjectId = ""; @Prop() toProjectId = ""; @Prop() isFromProjectView = false; + @Prop() hideShowAll = false; @Prop({ default: "person" }) giverEntityType = "person" as | "person" | "project"; @@ -224,15 +227,6 @@ export default class GiftedDialog extends Vue { this.allMyDids = await retrieveAccountDids(); - if (this.giver && !this.giver.name) { - this.giver.name = didInfo( - this.giver.did, - this.activeDid, - this.allMyDids, - this.allContacts, - ); - } - if ( this.giverEntityType === "project" || this.recipientEntityType === "project" @@ -455,14 +449,14 @@ export default class GiftedDialog extends Vue { if (contact) { this.giver = { did: contact.did, - name: contact.name || contact.did, + name: contact.name, }; } else { // Only set to "Unnamed" if no giver is currently set if (!this.giver || !this.giver.did) { this.giver = { did: "", - name: "Unnamed", + name: UNNAMED_ENTITY_NAME, }; } } @@ -517,14 +511,14 @@ export default class GiftedDialog extends Vue { if (contact) { this.receiver = { did: contact.did, - name: contact.name || contact.did, + name: contact.name, }; } else { // Only set to "Unnamed" if no receiver is currently set if (!this.receiver || !this.receiver.did) { this.receiver = { did: "", - name: "Unnamed", + name: UNNAMED_ENTITY_NAME, }; } } @@ -566,20 +560,21 @@ export default class GiftedDialog extends Vue { /** * Handle entity selection from EntitySelectionStep - * @param entity - The selected entity (person, project, or special) with stepType + * @param entity - The selected entity (person or project) with stepType */ handleEntitySelected(entity: { - type: "person" | "project" | "special"; - entityType?: string; - data: Contact | PlanData | { did?: string; name: string }; + type: "person" | "project"; + data: Contact | PlanData; stepType: string; }) { if (entity.type === "person") { const contact = entity.data as Contact; + // Apply DID-based logic for person entities + const processedContact = this.processPersonEntity(contact); if (entity.stepType === "giver") { - this.selectGiver(contact); + this.selectGiver(processedContact); } else { - this.selectRecipient(contact); + this.selectRecipient(processedContact); } } else if (entity.type === "project") { const project = entity.data as PlanData; @@ -588,33 +583,22 @@ export default class GiftedDialog extends Vue { } else { this.selectRecipientProject(project); } - } else if (entity.type === "special") { - // Handle special entities like "You" and "Unnamed" - if (entity.entityType === "you") { - // "You" entity selected - const youEntity = { - did: this.activeDid, - name: "You", - }; - if (entity.stepType === "giver") { - this.giver = youEntity; - } else { - this.receiver = youEntity; - } - this.firstStep = false; - } else if (entity.entityType === "unnamed") { - // "Unnamed" entity selected - const unnamedEntity = { - did: "", - name: "Unnamed", - }; - if (entity.stepType === "giver") { - this.giver = unnamedEntity; - } else { - this.receiver = unnamedEntity; - } - this.firstStep = false; - } + } + } + + /** + * Processes person entities using DID-based logic for "You" and "Unnamed" + */ + private processPersonEntity(contact: Contact): Contact { + if (contact.did === this.activeDid) { + // If DID matches active DID, create "You" entity + return { ...contact, name: "You" }; + } else if (!contact.did || contact.did === "") { + // If DID is empty/null, create "Unnamed" entity + return { ...contact, name: UNNAMED_ENTITY_NAME }; + } else { + // Return the contact as-is + return contact; } } diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index 89840ebd..ed6b1a32 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -81,7 +81,7 @@

- {{ member.name || "Unnamed Member" }} + {{ member.name || unnamedMember }}

= []; + /** + * Get the unnamed member constant + */ + get unnamedMember(): string { + return SOMEONE_UNNAMED; + } + async created() { this.notify = createNotifyHelpers(this.$notify); diff --git a/src/components/PersonCard.vue b/src/components/PersonCard.vue index 1f3bf595..bf2b72b9 100644 --- a/src/components/PersonCard.vue +++ b/src/components/PersonCard.vue @@ -25,7 +25,7 @@ conflict detection. * * @author Matthew Raymer */

- {{ person.name || person.did || "Unnamed" }} + {{ displayName }}

@@ -98,9 +98,27 @@ export default class PersonCard extends Vue { return `${baseClasses} text-slate-400`; } + // Add italic styling for entities without set names + if (!this.person.name) { + return `${baseClasses} italic text-slate-500`; + } + return baseClasses; } + /** + * Computed display name for the person + */ + get displayName(): string { + // If the entity has a set name, use that name + if (this.person.name) { + return this.person.name; + } + + // If the entity does not have a set name + return this.person.did; + } + /** * Handle card click - emit if selectable and not conflicted, show warning if conflicted */ @@ -114,7 +132,7 @@ export default class PersonCard extends Vue { group: "alert", type: "warning", title: "Cannot Select", - text: `You cannot select "${this.person.name || this.person.did || "Unnamed"}" because they are already selected as the ${this.conflictContext}.`, + text: `You cannot select "${this.displayName}" because they are already selected as the ${this.conflictContext}.`, }, 3000, ); diff --git a/src/components/ProjectCard.vue b/src/components/ProjectCard.vue index 7f09eda8..f0ba9e6c 100644 --- a/src/components/ProjectCard.vue +++ b/src/components/ProjectCard.vue @@ -15,7 +15,7 @@ issuer information. * * @author Matthew Raymer */

- {{ project.name || "Unnamed Project" }} + {{ project.name || unnamedProject }}

@@ -31,6 +31,7 @@ import ProjectIcon from "./ProjectIcon.vue"; import { PlanData } from "../interfaces/records"; import { Contact } from "../db/tables/contacts"; import { didInfo } from "../libs/endorserServer"; +import { UNNAMED_PROJECT } from "@/constants/entities"; /** * ProjectCard - Displays a project entity with selection capability @@ -63,6 +64,13 @@ export default class ProjectCard extends Vue { @Prop({ required: true }) allContacts!: Contact[]; + /** + * Get the unnamed project constant + */ + get unnamedProject(): string { + return UNNAMED_PROJECT; + } + /** * Computed display name for the project issuer */ diff --git a/src/components/PushNotificationPermission.vue b/src/components/PushNotificationPermission.vue index 7372c63d..37b266a0 100644 --- a/src/components/PushNotificationPermission.vue +++ b/src/components/PushNotificationPermission.vue @@ -115,6 +115,7 @@ import { urlBase64ToUint8Array } from "../libs/crypto/vc/util"; import * as libsUtil from "../libs/util"; import { logger } from "../utils/logger"; import { PlatformServiceMixin } from "../utils/PlatformServiceMixin"; +import { UNNAMED_ENTITY_NAME } from "@/constants/entities"; // Example interface for error interface ErrorResponse { @@ -602,7 +603,7 @@ export default class PushNotificationPermission extends Vue { * Returns the default message for direct push */ get notificationMessagePlaceholder(): string { - return "Click to share some gratitude with the world -- even if they're unnamed."; + return `Click to share some gratitude with the world -- even if they're ${UNNAMED_ENTITY_NAME.toLowerCase()}.`; } /** diff --git a/src/components/SpecialEntityCard.vue b/src/components/SpecialEntityCard.vue index 1d475229..e489d003 100644 --- a/src/components/SpecialEntityCard.vue +++ b/src/components/SpecialEntityCard.vue @@ -124,8 +124,6 @@ export default class SpecialEntityCard extends Vue { handleClick(): void { if (this.selectable && !this.conflicted) { this.emitEntitySelected({ - type: "special", - entityType: this.entityType, data: this.entityData, }); } else if (this.conflicted && this.notify) { @@ -145,13 +143,7 @@ export default class SpecialEntityCard extends Vue { // Emit methods using @Emit decorator @Emit("entity-selected") - emitEntitySelected(data: { - type: string; - entityType: string; - data: { did?: string; name: string }; - }): { - type: string; - entityType: string; + emitEntitySelected(data: { data: { did?: string; name: string } }): { data: { did?: string; name: string }; } { return data; diff --git a/src/constants/entities.ts b/src/constants/entities.ts new file mode 100644 index 00000000..62b9af5a --- /dev/null +++ b/src/constants/entities.ts @@ -0,0 +1,14 @@ +/** + * Constants for entity-related strings, particularly for unnamed/unknown person entities + */ + +// Core unnamed entity names +export const UNNAMED_ENTITY_NAME = "Unnamed"; + +// Descriptive phrases for unnamed entities +export const SOMEONE_UNNAMED = "Someone Unnamed"; +export const THAT_UNNAMED_PERSON = "That unnamed person"; +export const UNNAMED_PERSON = "unnamed person"; + +// Project-related unnamed entities +export const UNNAMED_PROJECT = "Unnamed Project"; diff --git a/src/constants/notifications.ts b/src/constants/notifications.ts index 8b5c3825..49b14812 100644 --- a/src/constants/notifications.ts +++ b/src/constants/notifications.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { THAT_UNNAMED_PERSON } from "./entities"; // Notification message constants for user-facing notifications // Add new notification messages here as needed @@ -873,7 +874,7 @@ export const NOTIFY_CONTACT_LINK_COPIED = { // Template for registration success message // Used in: ContactsView.vue (register method - registration success with contact name) export const getRegisterPersonSuccessMessage = (name?: string): string => - `${name || "That unnamed person"} ${NOTIFY_REGISTER_PERSON_SUCCESS.message}`; + `${name || THAT_UNNAMED_PERSON} ${NOTIFY_REGISTER_PERSON_SUCCESS.message}`; // Template for visibility success message // Used in: ContactsView.vue (setVisibility method - visibility success with contact name) @@ -1378,7 +1379,7 @@ export function createQRContactAddedMessage(hasVisibility: boolean): string { export function createQRRegistrationSuccessMessage( contactName: string, ): string { - return `${contactName || "That unnamed person"}${NOTIFY_QR_REGISTRATION_SUCCESS.message}`; + return `${contactName || THAT_UNNAMED_PERSON}${NOTIFY_QR_REGISTRATION_SUCCESS.message}`; } // ContactQRScanShowView.vue timeout constants diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index a543ea8d..6b0d2792 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -60,6 +60,7 @@ import { PlanSummaryRecord } from "../interfaces/records"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { APP_SERVER } from "@/constants/app"; +import { SOMEONE_UNNAMED } from "@/constants/entities"; /** * Standard context for schema.org data @@ -309,7 +310,7 @@ export function didInfoForContact( showDidForVisible: boolean = false, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): { known: boolean; displayName: string; profileImageUrl?: string } { - if (!did) return { displayName: "Someone Not Named", known: false }; + if (!did) return { displayName: SOMEONE_UNNAMED, known: false }; if (did === activeDid) { return { displayName: "You", known: true }; } else if (contact) { diff --git a/src/libs/util.ts b/src/libs/util.ts index e21d1932..f17a1062 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -33,6 +33,7 @@ import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "../services/PlatformServiceFactory"; import { IIdentifier } from "@veramo/core"; import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto"; +import { UNNAMED_PERSON } from "@/constants/entities"; // Consolidate this with src/utils/PlatformServiceMixin.mapQueryResultToValues function mapQueryResultToValues( @@ -192,7 +193,7 @@ export const nameForContact = ( ): string => { return ( (contact?.name as string) || - (capitalize ? "This" : "this") + " unnamed user" + (capitalize ? "This" : "this") + " " + UNNAMED_PERSON ); }; diff --git a/src/views/ContactGiftingView.vue b/src/views/ContactGiftingView.vue index 403a1fbe..21adf7cf 100644 --- a/src/views/ContactGiftingView.vue +++ b/src/views/ContactGiftingView.vue @@ -17,20 +17,40 @@
    + +
  • +

    + + + You + + + + +

    +
  • - (Not Named) + {{ + unnamedEntityName + }} @@ -43,14 +63,22 @@ class="border-b border-slate-300 py-3" >

    - + - {{ contact.name }} - (No name) + {{ + contact.name + }} + {{ contact.did }}

@@ -207,7 +207,7 @@ class="text-blue-500" @click="onClickLoadProject(fulfilledByThis.handleId)" > - {{ fulfilledByThis.name || "Unnamed Project" }} + {{ fulfilledByThis.name || unnamedProject }}
@@ -611,6 +611,7 @@ import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; import { NOTIFY_CONFIRM_CLAIM } from "@/constants/notifications"; import { APP_SERVER } from "@/constants/app"; +import { UNNAMED_PROJECT } from "@/constants/entities"; /** * Project View Component * @author Matthew Raymer @@ -664,6 +665,13 @@ export default class ProjectViewView extends Vue { /** Notification helpers instance */ notify!: ReturnType; + /** + * Get the unnamed project constant + */ + get unnamedProject(): string { + return UNNAMED_PROJECT; + } + // Account and Settings State /** Currently active DID */ activeDid = ""; diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index e8709210..9af4aaef 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -244,7 +244,7 @@

- {{ project.name || "Unnamed Project" }} + {{ project.name || unnamedProject }}

{{ project.description }} @@ -286,6 +286,7 @@ import { NOTIFY_OFFERS_LOAD_ERROR, NOTIFY_OFFERS_FETCH_ERROR, } from "@/constants/notifications"; +import { UNNAMED_PROJECT } from "@/constants/entities"; /** * Projects View Component @@ -324,6 +325,13 @@ export default class ProjectsView extends Vue { notify!: ReturnType; + /** + * Get the unnamed project constant + */ + get unnamedProject(): string { + return UNNAMED_PROJECT; + } + // User account state activeDid = ""; allContacts: Array = []; diff --git a/test-playwright/00-noid-tests.spec.ts b/test-playwright/00-noid-tests.spec.ts index e4b874af..a1180740 100644 --- a/test-playwright/00-noid-tests.spec.ts +++ b/test-playwright/00-noid-tests.spec.ts @@ -69,6 +69,7 @@ */ import { test, expect } from '@playwright/test'; +import { UNNAMED_ENTITY_NAME } from '../src/constants/entities'; import { deleteContact, generateAndRegisterEthrUser, importUser } from './testUtils'; test('Check activity feed - check that server is running', async ({ page }) => { @@ -177,7 +178,7 @@ test('Check User 0 can register a random person', async ({ page }) => { await page.goto('./'); await page.getByTestId('closeOnboardingAndFinish').click(); await page.getByRole('button', { name: 'Person' }).click(); - await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click(); + await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click(); await page.getByPlaceholder('What was given').fill('Gave me access!'); await page.getByRole('button', { name: 'Sign & Send' }).click(); await expect(page.getByText('That gift was recorded.')).toBeVisible(); diff --git a/test-playwright/30-record-gift.spec.ts b/test-playwright/30-record-gift.spec.ts index d8ee9698..cd942236 100644 --- a/test-playwright/30-record-gift.spec.ts +++ b/test-playwright/30-record-gift.spec.ts @@ -79,6 +79,7 @@ * ``` */ import { test, expect } from '@playwright/test'; +import { UNNAMED_ENTITY_NAME } from '../src/constants/entities'; import { importUser } from './testUtils'; test('Record something given', async ({ page }) => { @@ -101,7 +102,7 @@ test('Record something given', async ({ page }) => { await page.goto('./'); await page.getByTestId('closeOnboardingAndFinish').click(); await page.getByRole('button', { name: 'Person' }).click(); - await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click(); + await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click(); await page.getByPlaceholder('What was given').fill(finalTitle); await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString()); await page.getByRole('button', { name: 'Sign & Send' }).click(); diff --git a/test-playwright/33-record-gift-x10.spec.ts b/test-playwright/33-record-gift-x10.spec.ts index f0dfeef4..37c5eb02 100644 --- a/test-playwright/33-record-gift-x10.spec.ts +++ b/test-playwright/33-record-gift-x10.spec.ts @@ -85,6 +85,7 @@ */ import { test, expect } from '@playwright/test'; +import { UNNAMED_ENTITY_NAME } from '../src/constants/entities'; import { importUser, createUniqueStringsArray, createRandomNumbersArray } from './testUtils'; test('Record 9 new gifts', async ({ page }) => { @@ -116,7 +117,7 @@ test('Record 9 new gifts', async ({ page }) => { await page.getByTestId('closeOnboardingAndFinish').click(); } await page.getByRole('button', { name: 'Person' }).click(); - await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click(); + await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click(); await page.getByPlaceholder('What was given').fill(finalTitles[i]); await page.getByRole('spinbutton').fill(finalNumbers[i].toString()); await page.getByRole('button', { name: 'Sign & Send' }).click();