From a142737771ade4112dd38bfd6d4dd43ead5ee8a8 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Wed, 5 Nov 2025 20:20:43 +0800 Subject: [PATCH] feat: replace authorized representative input with contact selection dialog Replace the plain text input for authorized representative with an interactive contact selection interface that provides better UX and maintains data consistency. Changes: - Add ProjectRepresentativeDialog component using EntityGrid for contact selection (excludes "You" and "Unnamed" special entities) - Replace text input with clickable field showing contact icon, name, and DID - Implement conditional UI states: initial "Assign..." placeholder vs assigned representative display with unset button - Refactor selectedRepresentative to computed property derived from agentDid (single source of truth, prevents sync issues) - Inline representativeDisplayName for simplicity - Support changing representative by clicking on assigned field - Support unsetting representative via trash button The new implementation ensures agentDid remains the authoritative state while selectedRepresentative is automatically computed, preventing the previously possible desync when agentDid was set directly (e.g., via the "make original owner an authorized representative" button). --- .../ProjectRepresentativeDialog.vue | 128 ++++++++++++++++++ src/views/NewEditProjectView.vue | 126 ++++++++++++++++- 2 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 src/components/ProjectRepresentativeDialog.vue diff --git a/src/components/ProjectRepresentativeDialog.vue b/src/components/ProjectRepresentativeDialog.vue new file mode 100644 index 00000000..c15b2dcd --- /dev/null +++ b/src/components/ProjectRepresentativeDialog.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index d732045f..29362324 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -60,12 +60,62 @@ - +
+
+
+ + +
+
+
+ {{ + selectedRepresentative + ? selectedRepresentative.name || AppString.NO_CONTACT_NAME + : "Assign Authorized Representative…" + }} +
+
+ {{ agentDid }} +
+
+
+ +
+ + +

Beware! @@ -232,9 +282,12 @@ import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet"; import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import { LeafletMouseEvent } from "leaflet"; +import EntityIcon from "../components/EntityIcon.vue"; import ImageMethodDialog from "../components/ImageMethodDialog.vue"; +import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue"; import QuickNav from "../components/QuickNav.vue"; import { + AppString, DEFAULT_IMAGE_API_SERVER, DEFAULT_PARTNER_API_SERVER, NotificationIface, @@ -268,6 +321,7 @@ import { retrieveAccountCount, retrieveFullyDecryptedAccount, } from "../libs/util"; +import { Contact } from "../db/tables/contacts"; import { EventTemplate, @@ -323,7 +377,15 @@ import { logger } from "../utils/logger"; */ @Component({ - components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav }, + components: { + EntityIcon, + ImageMethodDialog, + ProjectRepresentativeDialog, + LMap, + LMarker, + LTileLayer, + QuickNav, + }, mixins: [PlatformServiceMixin], }) export default class NewEditProjectView extends Vue { @@ -334,6 +396,9 @@ export default class NewEditProjectView extends Vue { // Notification helpers private notify!: ReturnType; + // Constants + AppString = AppString; + /** * Display error notification to user * Provides consistent error messaging with 5-second timeout @@ -346,6 +411,8 @@ export default class NewEditProjectView extends Vue { // Component state properties activeDid = ""; agentDid = ""; + allContacts: Array = []; + allMyDids: string[] = []; apiServer = ""; endDateInput?: string; endTimeInput?: string; @@ -392,6 +459,14 @@ export default class NewEditProjectView extends Vue { const activeIdentity = await (this as any).$getActiveIdentity(); this.activeDid = activeIdentity.activeDid || ""; + // Get all user's DIDs + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.allMyDids = await (this as any).$getAllAccountDids(); + + // Load contacts + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.allContacts = await (this as any).$getAllContacts(); + this.apiServer = settings.apiServer || ""; this.showGeneralAdvanced = !!settings.showGeneralAdvanced; @@ -961,5 +1036,44 @@ export default class NewEditProjectView extends Vue { get shouldShowSpinner(): boolean { return !this.isHiddenSpinner; } + + /** + * Computed property for selected representative contact + * Derives the contact from agentDid by finding it in allContacts + */ + get selectedRepresentative(): Contact | null { + if (!this.agentDid) { + return null; + } + return this.allContacts.find((c) => c.did === this.agentDid) || null; + } + + /** + * Open the representative selection dialog + */ + openRepresentativeDialog(): void { + (this.$refs.representativeDialog as ProjectRepresentativeDialog).open(); + } + + /** + * Handle representative assignment from dialog + */ + handleRepresentativeAssigned(contact: Contact): void { + this.agentDid = contact.did; + } + + /** + * Handle representative dialog cancel + */ + handleRepresentativeCancel(): void { + // Dialog closes itself, nothing to do here + } + + /** + * Unset the representative and revert to initial state + */ + unsetRepresentative(): void { + this.agentDid = ""; + } }