diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index e3a3370..69010cb 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -13,14 +13,14 @@ export type BoundingBox = { */ export type Settings = { // default entry is keyed with MASTER_SETTINGS_KEY; other entries are linked to an account with account ID - id?: number; // this is only blank on input, when the database assigns it + id?: number; // this is erased for all those entries that are keyed with accountDid // if supplied, this settings record overrides the master record when the user switches to this account accountDid?: string; // not used in the MASTER_SETTINGS_KEY entry // active Decentralized ID activeDid?: string; // only used in the MASTER_SETTINGS_KEY entry - apiServer?: string; // API server URL + apiServer: string; // API server URL filterFeedByNearby?: boolean; // filter by nearby filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden @@ -29,7 +29,7 @@ export type Settings = { firstName?: string; // user's full name, may be null if unwanted for a particular account hideRegisterPromptOnNewContact?: boolean; isRegistered?: boolean; - imageServer?: string; + // imageServer?: string; // if we want to allow modification then we should make image functionality optional -- or at least customizable lastName?: string; // deprecated - put all names in firstName lastAckedOfferToUserJwtId?: string; // the last JWT ID for offer-to-user that they've acknowledged seeing diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 4255e90..5c3af0d 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -191,13 +191,9 @@ export interface PlanVerifiableCredential extends GenericVerifiableCredential { * Represents data about a project * * @deprecated - * We should use PlanSummaryRecord instead. + * We should use PlanSummaryRecord instead, adding rowid to it. **/ export interface PlanData { - /** - * Name of the project - **/ - name: string; /** * Description of the project **/ @@ -211,6 +207,10 @@ export interface PlanData { * The DID of the issuer */ issuerDid: string; + /** + * Name of the project + **/ + name: string; /** * The identifier of the project -- different from jwtId, needs to be fixed **/ diff --git a/src/libs/partnerServer.ts b/src/libs/partnerServer.ts new file mode 100644 index 0000000..08172a4 --- /dev/null +++ b/src/libs/partnerServer.ts @@ -0,0 +1,7 @@ +export interface UserProfile { + description: string; + locLat?: number; + locLon?: number; + issuerDid: string; + rowid?: string; +} diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 7b88132..a0c1e40 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -82,13 +82,12 @@
@@ -250,16 +249,15 @@
-
Location for Searches
+ Location for Searches - Set Search Area… - + {{ isSearchAreasSet ? "Change" : "Set" }} Search Area…
@@ -285,10 +283,7 @@ :class="{ 'bg-slate-100': loadingProfile || savingProfile }" > -
+
Notification Push Server -
+
-
+
Image Server URL   {{ DEFAULT_IMAGE_API_SERVER }} @@ -930,6 +925,7 @@ import { DIRECT_PUSH_TITLE, retrieveAccountMetadata, } from "@/libs/util"; +import { UserProfile } from "@/libs/partnerServer"; const inputImportFileNameRef = ref(); @@ -964,9 +960,9 @@ export default class AccountViewView extends Vue { givenName = ""; hideRegisterPromptOnNewContact = false; imageLimits: ImageRateLimits | null = null; - imageServer = ""; includeUserProfileLocation = false; isRegistered = false; + isSearchAreasSet = false; limitsMessage = ""; loadingLimits = false; loadingProfile = true; @@ -975,8 +971,8 @@ export default class AccountViewView extends Vue { notifyingReminder = false; notifyingReminderMessage = ""; notifyingReminderTime = ""; - partnerApiServer = ""; - partnerApiServerInput = ""; + partnerApiServer = DEFAULT_PARTNER_API_SERVER; + partnerApiServerInput = DEFAULT_PARTNER_API_SERVER; passkeyExpirationDescription = ""; passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES; previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES; @@ -997,8 +993,8 @@ export default class AccountViewView extends Vue { subscription: PushSubscription | null = null; warnIfProdServer = false; warnIfTestServer = false; - webPushServer = ""; - webPushServerInput = ""; + webPushServer = DEFAULT_PUSH_SERVER; + webPushServerInput = DEFAULT_PUSH_SERVER; userProfileDesc = ""; userProfileLatitude = 0; userProfileLongitude = 0; @@ -1021,7 +1017,7 @@ export default class AccountViewView extends Vue { try { const headers = await getHeaders(this.activeDid); const response = await this.axios.get( - this.apiServer + "/api/partner/user-profile/" + this.activeDid, + this.apiServer + "/api/partner/userProfile/" + this.activeDid, { headers }, ); if (response.status === 200) { @@ -1127,14 +1123,15 @@ export default class AccountViewView extends Vue { this.hideRegisterPromptOnNewContact = !!settings.hideRegisterPromptOnNewContact; this.isRegistered = !!settings?.isRegistered; - this.imageServer = settings.imageServer || ""; + this.isSearchAreasSet = !!settings.searchBoxes; this.notifyingNewActivity = !!settings.notifyingNewActivityTime; this.notifyingNewActivityTime = settings.notifyingNewActivityTime || ""; this.notifyingReminder = !!settings.notifyingReminderTime; this.notifyingReminderMessage = settings.notifyingReminderMessage || ""; this.notifyingReminderTime = settings.notifyingReminderTime || ""; - this.partnerApiServer = settings.partnerApiServer || ""; - this.partnerApiServerInput = settings.partnerApiServer || ""; + this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer; + this.partnerApiServerInput = + settings.partnerApiServer || this.partnerApiServerInput; this.profileImageUrl = settings.profileImageUrl; this.showContactGives = !!settings.showContactGivesInline; this.passkeyExpirationMinutes = @@ -1144,8 +1141,8 @@ export default class AccountViewView extends Vue { this.showShortcutBvc = !!settings.showShortcutBvc; this.warnIfProdServer = !!settings.warnIfProdServer; this.warnIfTestServer = !!settings.warnIfTestServer; - this.webPushServer = settings.webPushServer || ""; - this.webPushServerInput = settings.webPushServer || ""; + this.webPushServer = settings.webPushServer || this.webPushServer; + this.webPushServerInput = settings.webPushServer || this.webPushServerInput; } // call fn, copy text to the clipboard, then redo fn after 2 seconds @@ -1793,15 +1790,25 @@ export default class AccountViewView extends Vue { this.savingProfile = true; try { const headers = await getHeaders(this.activeDid); - const payload = { + const payload: UserProfile = { description: this.userProfileDesc, }; if (this.userProfileLatitude && this.userProfileLongitude) { payload.locLat = this.userProfileLatitude; payload.locLon = this.userProfileLongitude; + } else if (this.includeUserProfileLocation) { + this.$notify( + { + group: "alert", + type: "toast", + title: "", + text: "No profile location is saved.", + }, + 3000, + ); } const response = await this.axios.post( - this.apiServer + "/api/partner/user-profile", + this.apiServer + "/api/partner/userProfile", payload, { headers }, ); @@ -1845,6 +1852,7 @@ export default class AccountViewView extends Vue { if (!this.includeUserProfileLocation) { this.userProfileLatitude = 0; this.userProfileLongitude = 0; + this.zoom = 2; } } @@ -1866,6 +1874,7 @@ export default class AccountViewView extends Vue { eraseLatLong() { this.userProfileLatitude = 0; this.userProfileLongitude = 0; + this.zoom = 2; this.includeUserProfileLocation = false; } } diff --git a/src/views/DiscoverView.vue b/src/views/DiscoverView.vue index 1ed38ee..791fcf5 100644 --- a/src/views/DiscoverView.vue +++ b/src/views/DiscoverView.vue @@ -6,7 +6,7 @@

- Discover Projects + Discover Projects & People

@@ -15,7 +15,6 @@
+ + + +
  • @@ -40,9 +77,10 @@ href="#" @click=" projects = []; + userProfiles = []; isLocalActive = true; isMappedActive = false; - isRemoteActive = false; + isAnywhereActive = false; isSearchVisible = true; tempSearchBox = null; searchLocal(); @@ -65,15 +103,17 @@ href="#" @click=" projects = []; + userProfiles = []; isLocalActive = false; isMappedActive = true; - isRemoteActive = false; + isAnywhereActive = false; isSearchVisible = false; searchTerms = ''; tempSearchBox = null; " v-bind:class="computedMappedTabStyleClassNames()" > + Mapped
  • @@ -82,9 +122,10 @@ href="#" @click=" projects = []; + userProfiles = []; isLocalActive = false; isMappedActive = false; - isRemoteActive = true; + isAnywhereActive = true; isSearchVisible = true; tempSearchBox = null; searchAll(); @@ -95,7 +136,7 @@ - No projects were found with that search.

    @@ -158,35 +199,68 @@
@@ -197,14 +271,14 @@ import "leaflet/dist/leaflet.css"; import * as L from "leaflet"; import { Component, Vue } from "vue-facing-decorator"; import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet"; -import { Router } from "vue-router"; +import { Router, RouteLocationNormalizedLoaded } from "vue-router"; import QuickNav from "@/components/QuickNav.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue"; import ProjectIcon from "@/components/ProjectIcon.vue"; import OnboardingDialog from "@/components/OnboardingDialog.vue"; import TopMessage from "@/components/TopMessage.vue"; -import { NotificationIface } from "@/constants/app"; +import { DEFAULT_PARTNER_API_SERVER, NotificationIface } from "@/constants/app"; import { db, logConsoleAndDb, @@ -218,8 +292,19 @@ import { getHeaders, PlanData, } from "@/libs/endorserServer"; +import { UserProfile } from "@/libs/partnerServer"; import { OnboardPage, retrieveAccountDids } from "@/libs/util"; +interface Tile { + indexLat: number; + indexLon: number; + minFoundLat: number; + maxFoundLat: number; + minFoundLon: number; + maxFoundLon: number; + recordCount: number; +} + @Component({ components: { InfiniteScroll, @@ -233,25 +318,31 @@ import { OnboardPage, retrieveAccountDids } from "@/libs/util"; }) export default class DiscoverView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; + $router!: Router; + $route!: RouteLocationNormalizedLoaded; activeDid = ""; allContacts: Array = []; allMyDids: Array = []; apiServer = ""; - searchTerms = ""; - projects: PlanData[] = []; isLoading = false; isLocalActive = true; isMappedActive = false; - isRemoteActive = false; + isAnywhereActive = false; + isProjectsActive = true; + isPeopleActive = false; isSearchVisible = true; localCenterLat = 0; localCenterLong = 0; localCount = -1; markers: { [key: string]: L.Marker } = {}; + partnerApiServer = DEFAULT_PARTNER_API_SERVER; + projects: PlanData[] = []; remoteCount = -1; searchBox: { name: string; bbox: BoundingBox } | null = null; + searchTerms = ""; tempSearchBox: BoundingBox | null = null; + userProfiles: UserProfile[] = []; zoomedSoDoNotMove = false; // make this function available to the Vue template @@ -261,13 +352,15 @@ export default class DiscoverView extends Vue { const settings = await retrieveSettingsForActiveAccount(); this.activeDid = (settings.activeDid as string) || ""; this.apiServer = (settings.apiServer as string) || ""; + this.partnerApiServer = + (settings.partnerApiServer as string) || this.partnerApiServer; this.searchBox = settings.searchBoxes?.[0] || null; this.allContacts = await db.contacts.toArray(); this.allMyDids = await retrieveAccountDids(); - this.searchTerms = (this.$route as Router).query["searchText"] || ""; + this.searchTerms = this.$route.query["searchText"]?.toString() || ""; if (!settings.finishedOnboarding) { (this.$refs.onboardingDialog as OnboardingDialog).open( @@ -284,7 +377,7 @@ export default class DiscoverView extends Vue { } else { this.isLocalActive = false; this.isMappedActive = false; - this.isRemoteActive = true; + this.isAnywhereActive = true; await this.searchAll(); } } @@ -298,8 +391,8 @@ export default class DiscoverView extends Vue { if (this.isLocalActive) { await this.searchLocal(); } else if (this.isMappedActive) { - this.isRemoteActive = true; - await this.searchAll(); + const mapRef = this.$refs.projectMap as L.Map; + this.requestTiles(mapRef.leafletObject); // not ideal because I found this from experimentation, not documentation } else { await this.searchAll(); } @@ -311,6 +404,7 @@ export default class DiscoverView extends Vue { if (!beforeId) { // this was an initial search so clear any previous results this.projects = []; + this.userProfiles = []; } let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms); @@ -319,62 +413,62 @@ export default class DiscoverView extends Vue { queryParams = queryParams + `&beforeId=${beforeId}`; } + const endpoint = this.isProjectsActive + ? this.apiServer + "/api/v2/report/plans" + : this.partnerApiServer + "/api/partner/userProfile"; + try { this.isLoading = true; - const response = await fetch( - this.apiServer + "/api/v2/report/plans?" + queryParams, - { - method: "GET", - headers: await getHeaders(this.activeDid), - }, - ); + const response = await fetch(endpoint + "?" + queryParams, { + method: "GET", + headers: await getHeaders(this.activeDid), + }); if (response.status !== 200) { const details = await response.text(); - console.error("Problem with full search:", details); - this.$notify( - { - group: "alert", - type: "danger", - title: "Error", - text: `There was a problem accessing the server.`, - }, - 3000, - ); - throw details; } const results = await response.json(); - const plans: PlanData[] = results.data; - if (plans) { - for (const plan of plans) { - const { name, description, handleId, image, issuerDid, rowid } = plan; - this.projects.push({ - name, - description, - handleId, - image, - issuerDid, - rowid, - }); + if (this.isProjectsActive) { + const plans: PlanData[] = results.data; + if (plans) { + for (const plan of plans) { + const { name, description, handleId, image, issuerDid, rowid } = plan; + this.projects.push({ + name, + description, + handleId, + image, + issuerDid, + rowid, + }); + } + this.remoteCount = this.projects.length; + } else { + throw JSON.stringify(results); } - this.remoteCount = this.projects.length; } else { - throw JSON.stringify(results); + const profiles: UserProfile[] = results.data; + if (profiles) { + this.userProfiles.push(...profiles); + this.remoteCount = this.userProfiles.length; + } else { + throw JSON.stringify(results); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - console.error("Error with feed load:", e); + console.error("Error with search all:", e); // this sometimes gives different information - console.error("Error with feed load (error added): " + e); + console.error("Error with search all (error added): " + e); this.$notify( { group: "alert", type: "danger", - title: "Error", - text: e.userMessage || "There was a problem retrieving projects.", + title: "Error Searching", + text: e.userMessage || "There was a problem retrieving " + (this.isProjectsActive ? "projects" : "profiles") + ".", }, 5000, ); @@ -392,12 +486,14 @@ export default class DiscoverView extends Vue { if (!searchBox) { this.projects = []; + this.userProfiles = []; return; } if (!beforeId) { // this was an initial search so clear any previous results this.projects = []; + this.userProfiles = []; } const claimContents = @@ -407,70 +503,71 @@ export default class DiscoverView extends Vue { claimContents, "minLocLat=" + searchBox.minLat, "maxLocLat=" + searchBox.maxLat, - "westLocLon=" + searchBox.westLong, - "eastLocLon=" + searchBox.eastLong, + "minLocLon=" + searchBox.westLong, + "maxLocLon=" + searchBox.eastLong, ].join("&"); if (beforeId) { queryParams = queryParams + `&beforeId=${beforeId}`; } + const endpoint = this.isProjectsActive + ? this.apiServer + "/api/v2/report/plansByLocation" + : this.partnerApiServer + "/api/partner/userProfile"; + try { this.isLoading = true; - const response = await fetch( - this.apiServer + "/api/v2/report/plansByLocation?" + queryParams, - { - method: "GET", - headers: await getHeaders(this.activeDid), - }, - ); + const response = await fetch(endpoint + "?" + queryParams, { + method: "GET", + headers: await getHeaders(this.activeDid), + }); if (response.status !== 200) { const details = await response.text(); - console.error("Problem with nearby search:", details); - this.$notify( - { - group: "alert", - type: "danger", - title: "Error", - text: "There was a problem accessing the server.", - }, - 3000, - ); - throw await response.text(); + throw details; } const results = await response.json(); - if (results.data) { - if (beforeId) { - const plans: PlanData[] = results.data; - for (const plan of plans) { - const { name, description, handleId, issuerDid, rowid } = plan; - this.projects.push({ - name, - description, - handleId, - issuerDid, - rowid, - }); + if (this.isProjectsActive) { + if (results.data) { + if (beforeId) { + const plans: PlanData[] = results.data; + for (const plan of plans) { + const { name, description, handleId, issuerDid, rowid } = plan; + this.projects.push({ + name, + description, + handleId, + issuerDid, + rowid, + }); + } + } else { + this.projects = results.data; } + this.localCount = this.projects.length; } else { - this.projects = results.data; + throw JSON.stringify(results); } - this.localCount = this.projects.length; } else { - throw JSON.stringify(results); + const profiles: UserProfile[] = results.data; + if (profiles) { + this.userProfiles.push(...profiles); + this.localCount = this.userProfiles.length; + } else { + throw JSON.stringify(results); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - console.error("Error with feed load:", e); + console.error("Error with search local:", e); this.$notify( { group: "alert", type: "danger", title: "Error", - text: e.userMessage || "There was a problem retrieving projects.", + text: e.userMessage || "There was a problem retrieving " + (this.isProjectsActive ? "projects" : "profiles") + ".", }, 5000, ); @@ -484,14 +581,21 @@ export default class DiscoverView extends Vue { * @param payload is the flag from the InfiniteScroll indicating if it should load **/ async loadMoreData(payload: boolean) { - if (this.projects.length > 0 && payload) { - const latestProject = this.projects[this.projects.length - 1]; - if (this.isLocalActive) { - this.searchLocal(latestProject["rowid"]); - } else if (this.isMappedActive) { - this.searchLocal(latestProject["rowid"]); - } else if (this.isRemoteActive) { - this.searchAll(latestProject["rowid"]); + if (payload) { + if (this.isProjectsActive && this.projects.length > 0) { + const latestProject = this.projects[this.projects.length - 1]; + if (this.isLocalActive || this.isMappedActive) { + this.searchLocal(latestProject.rowid); + } else if (this.isAnywhereActive) { + this.searchAll(latestProject.rowid); + } + } else if (!this.isProjectsActive && this.userProfiles.length > 0) { + const latestProfile = this.userProfiles[this.userProfiles.length - 1]; + if (this.isLocalActive || this.isMappedActive) { + this.searchLocal(latestProfile.rowid || ""); + } else if (this.isAnywhereActive) { + this.searchAll(latestProfile.rowid || ""); + } } } } @@ -503,7 +607,7 @@ export default class DiscoverView extends Vue { } // Tried but failed to use other vue-leaflet methods update:zoom and update:bounds - // To access the from this.$refs, use this.$refs.projectMap.mapObject + // To access the from this.$refs, use this.$refs.projectMap.leafletObject (or maybe mapObject) onMoveStart(/* event: L.LocationEvent */) { // don't remove markers because they follow the map when moving (and the experience is jarring) @@ -540,9 +644,10 @@ export default class DiscoverView extends Vue { "westLocLon=" + bounds?.getSouthWest().lng, "eastLocLon=" + bounds?.getNorthEast().lng, ].join("&"); - const response = await fetch( - this.apiServer + "/api/v2/report/planCountsByBBox?" + queryParams, - ); + const endpoint = this.isProjectsActive + ? this.apiServer + "/api/v2/report/planCountsByBBox" + : this.partnerApiServer + "/api/partner/userProfileCountsByBBox"; + const response = await fetch(endpoint + "?" + queryParams); if (response.status === 200) { Object.values(this.markers).forEach((marker) => marker.remove()); this.markers = {}; @@ -601,14 +706,16 @@ export default class DiscoverView extends Vue { } /** - * Handle clicking on a project entry found in the list - * @param id of the project + * Handle clicking on a project or profile entry found in the list + * @param id of the project or profile **/ - onClickLoadProject(id: string) { + onClickLoadItem(id: string) { const route = { - path: "/project/" + encodeURIComponent(id), + path: this.isProjectsActive + ? "/project/" + encodeURIComponent(id) + : "/userProfile/" + encodeURIComponent(id), }; - (this.$router as Router).push(route); + this.$router.push(route); } public computedLocalTabStyleClassNames() { @@ -654,14 +761,50 @@ export default class DiscoverView extends Vue { "rounded-t-lg": true, "border-b-2": true, - active: this.isRemoteActive, - "text-black": this.isRemoteActive, - "border-black": this.isRemoteActive, - "font-semibold": this.isRemoteActive, + active: this.isAnywhereActive, + "text-black": this.isAnywhereActive, + "border-black": this.isAnywhereActive, + "font-semibold": this.isAnywhereActive, + + "text-blue-600": !this.isAnywhereActive, + "border-transparent": !this.isAnywhereActive, + "hover:border-slate-400": !this.isAnywhereActive, + }; + } + + public computedProjectsTabStyleClassNames() { + return { + "inline-block": true, + "py-3": true, + "rounded-t-lg": true, + "border-b-2": true, + + active: this.isProjectsActive, + "text-black": this.isProjectsActive, + "border-black": this.isProjectsActive, + "font-semibold": this.isProjectsActive, + + "text-blue-600": !this.isProjectsActive, + "border-transparent": !this.isProjectsActive, + "hover:border-slate-400": !this.isProjectsActive, + }; + } + + public computedPeopleTabStyleClassNames() { + return { + "inline-block": true, + "py-3": true, + "rounded-t-lg": true, + "border-b-2": true, + + active: this.isPeopleActive, + "text-black": this.isPeopleActive, + "border-black": this.isPeopleActive, + "font-semibold": this.isPeopleActive, - "text-blue-600": !this.isRemoteActive, - "border-transparent": !this.isRemoteActive, - "hover:border-slate-400": !this.isRemoteActive, + "text-blue-600": !this.isPeopleActive, + "border-transparent": !this.isPeopleActive, + "hover:border-slate-400": !this.isPeopleActive, }; } } diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index e7c3c77..04d7d09 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -77,7 +77,9 @@ maxlength="5000" >
- If you want to be contacted, be sure to include your contact information. + If you want to be contacted, be sure to include your contact information + -- just remember that this information is public and saved in a public + history.
{{ fullClaim.description?.length }}/5000 max. characters