forked from trent_larson/crowd-funder-for-time-pwa
add page for user profile view and update endpoints; rename any "rowid" to "rowId"
This commit is contained in:
@@ -191,7 +191,7 @@ export interface PlanVerifiableCredential extends GenericVerifiableCredential {
|
|||||||
* Represents data about a project
|
* Represents data about a project
|
||||||
*
|
*
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* We should use PlanSummaryRecord instead, adding rowid to it.
|
* (Maybe we should use PlanSummaryRecord instead, either by adding rowId or by iterating with jwtId.)
|
||||||
**/
|
**/
|
||||||
export interface PlanData {
|
export interface PlanData {
|
||||||
/**
|
/**
|
||||||
@@ -212,9 +212,10 @@ export interface PlanData {
|
|||||||
**/
|
**/
|
||||||
name: string;
|
name: string;
|
||||||
/**
|
/**
|
||||||
* The identifier of the project -- different from jwtId, needs to be fixed
|
* The identifier of the project record -- different from jwtId
|
||||||
|
* (Maybe we should use the jwtId to iterate through the records instead.)
|
||||||
**/
|
**/
|
||||||
rowid?: string;
|
rowId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EndorserRateLimits {
|
export interface EndorserRateLimits {
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export interface UserProfile {
|
|||||||
locLat?: number;
|
locLat?: number;
|
||||||
locLon?: number;
|
locLon?: number;
|
||||||
issuerDid: string;
|
issuerDid: string;
|
||||||
rowid?: string;
|
rowId?: string; // set on profile retrieved from server
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,6 +258,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "test",
|
name: "test",
|
||||||
component: () => import("../views/TestView.vue"),
|
component: () => import("../views/TestView.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/userProfile/:id?",
|
||||||
|
name: "userProfile",
|
||||||
|
component: () => import("../views/UserProfileView.vue"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {*} */
|
/** @type {*} */
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
We'll just pop the message in only if we discover that they need it.
|
We'll just pop the message in only if we discover that they need it.
|
||||||
-->
|
-->
|
||||||
<div
|
<div
|
||||||
v-if="!loadingLimits && !endorserLimits?.nextWeekBeginDateTime"
|
v-if="!isRegistered"
|
||||||
id="noticeBeforeAnnounce"
|
id="noticeBeforeAnnounce"
|
||||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
|
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
|
||||||
>
|
>
|
||||||
@@ -175,6 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
v-if="isRegistered"
|
||||||
id="sectionNotifications"
|
id="sectionNotifications"
|
||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
>
|
>
|
||||||
@@ -262,7 +263,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
<div
|
||||||
|
v-if="isRegistered"
|
||||||
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
|
>
|
||||||
<div v-if="loadingProfile" class="text-center mb-2">
|
<div v-if="loadingProfile" class="text-center mb-2">
|
||||||
<fa icon="spinner" class="fa-spin text-slate-400"></fa> Loading
|
<fa icon="spinner" class="fa-spin text-slate-400"></fa> Loading
|
||||||
profile...
|
profile...
|
||||||
@@ -321,22 +325,39 @@
|
|||||||
/>
|
/>
|
||||||
</l-map>
|
</l-map>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div v-if="!loadingProfile && !savingProfile">
|
||||||
@click="saveProfile"
|
<div class="flex justify-between items-center">
|
||||||
class="mt-2 px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
<button
|
||||||
:disabled="loadingProfile || savingProfile"
|
@click="saveProfile"
|
||||||
:class="{
|
class="mt-2 px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
||||||
'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
|
:disabled="loadingProfile || savingProfile"
|
||||||
}"
|
:class="{
|
||||||
>
|
'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
|
||||||
{{
|
}"
|
||||||
loadingProfile
|
>
|
||||||
? "Loading..."
|
Save Profile
|
||||||
: savingProfile
|
</button>
|
||||||
? "Saving..."
|
<button
|
||||||
: "Save Profile"
|
@click="confirmDeleteProfile"
|
||||||
}}
|
class="mt-2 px-4 py-2 bg-gradient-to-b from-red-400 to-red-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
||||||
</button>
|
:disabled="loadingProfile || savingProfile"
|
||||||
|
:class="{
|
||||||
|
'opacity-50 cursor-not-allowed':
|
||||||
|
loadingProfile ||
|
||||||
|
savingProfile ||
|
||||||
|
(!userProfileDesc && !includeUserProfileLocation),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Delete Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="loadingProfile">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div v-else="savingProfile">
|
||||||
|
Saving...
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -1014,40 +1035,46 @@ export default class AccountViewView extends Vue {
|
|||||||
await this.processIdentity();
|
await this.processIdentity();
|
||||||
|
|
||||||
// Load the user profile
|
// Load the user profile
|
||||||
try {
|
if (this.isRegistered) {
|
||||||
const headers = await getHeaders(this.activeDid);
|
try {
|
||||||
const response = await this.axios.get(
|
const headers = await getHeaders(this.activeDid);
|
||||||
this.apiServer + "/api/partner/userProfile/" + this.activeDid,
|
const response = await this.axios.get(
|
||||||
{ headers },
|
this.apiServer +
|
||||||
);
|
"/api/partner/userProfileForIssuer/" +
|
||||||
if (response.status === 200) {
|
this.activeDid,
|
||||||
this.userProfileDesc = response.data.description || "";
|
{ headers },
|
||||||
this.userProfileLatitude = response.data.locLat || 0;
|
|
||||||
this.userProfileLongitude = response.data.locLon || 0;
|
|
||||||
if (this.userProfileLatitude && this.userProfileLongitude) {
|
|
||||||
this.includeUserProfileLocation = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// won't get here because axios throws an error instead
|
|
||||||
throw Error("Unable to load profile.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status === 404) {
|
|
||||||
// this is ok: the profile is not yet created
|
|
||||||
} else {
|
|
||||||
logConsoleAndDb("Error loading profile: " + errorStringForLog(error));
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Loading Profile",
|
|
||||||
text: "Your server profile is not available.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
);
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.userProfileDesc = response.data.data.description || "";
|
||||||
|
this.userProfileLatitude = response.data.data.locLat || 0;
|
||||||
|
this.userProfileLongitude = response.data.data.locLon || 0;
|
||||||
|
if (this.userProfileLatitude && this.userProfileLongitude) {
|
||||||
|
this.includeUserProfileLocation = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// won't get here because axios throws an error instead
|
||||||
|
throw Error("Unable to load profile.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 404) {
|
||||||
|
// this is ok: the profile is not yet created
|
||||||
|
} else {
|
||||||
|
logConsoleAndDb(
|
||||||
|
"Error loading profile: " + errorStringForLog(error),
|
||||||
|
);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Loading Profile",
|
||||||
|
text: "Your server profile is not available.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loadingProfile = false;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.loadingProfile = false;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this can happen when running automated tests in dev mode because notifications don't work
|
// this can happen when running automated tests in dev mode because notifications don't work
|
||||||
@@ -1877,5 +1904,64 @@ export default class AccountViewView extends Vue {
|
|||||||
this.zoom = 2;
|
this.zoom = 2;
|
||||||
this.includeUserProfileLocation = false;
|
this.includeUserProfileLocation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async confirmDeleteProfile() {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "modal",
|
||||||
|
type: "confirm",
|
||||||
|
title: "Delete Profile",
|
||||||
|
text: "Are you sure you want to delete your public profile? This will remove your description and location from the server, and it cannot be undone.",
|
||||||
|
onYes: this.deleteProfile,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProfile() {
|
||||||
|
this.savingProfile = true;
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(this.activeDid);
|
||||||
|
const response = await this.axios.delete(
|
||||||
|
this.apiServer + "/api/partner/userProfile",
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
if (response.status === 204) {
|
||||||
|
this.userProfileDesc = "";
|
||||||
|
this.userProfileLatitude = 0;
|
||||||
|
this.userProfileLongitude = 0;
|
||||||
|
this.includeUserProfileLocation = false;
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Profile Deleted",
|
||||||
|
text: "Your profile has been deleted successfully.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw Error("Profile not deleted");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb("Error deleting profile: " + errorStringForLog(error));
|
||||||
|
const errorMessage: string =
|
||||||
|
error.response?.data?.error?.message ||
|
||||||
|
error.response?.data?.error ||
|
||||||
|
error.message ||
|
||||||
|
"There was an error deleting your profile.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Deleting Profile",
|
||||||
|
text: errorMessage,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.savingProfile = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -184,7 +184,10 @@
|
|||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="projects.length === 0 && userProfiles.length === 0" class="text-center mt-8">
|
<div
|
||||||
|
v-else-if="projects.length === 0 && userProfiles.length === 0"
|
||||||
|
class="text-center mt-8"
|
||||||
|
>
|
||||||
<p class="text-lg text-slate-500">
|
<p class="text-lg text-slate-500">
|
||||||
<span v-if="isLocalActive">
|
<span v-if="isLocalActive">
|
||||||
<span v-if="searchBox"> None found in the selected area. </span>
|
<span v-if="searchBox"> None found in the selected area. </span>
|
||||||
@@ -224,7 +227,12 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||||
{{
|
{{
|
||||||
didInfo(project.issuerDid, activeDid, allMyDids, allContacts)
|
didInfo(
|
||||||
|
project.issuerDid,
|
||||||
|
activeDid,
|
||||||
|
allMyDids,
|
||||||
|
allContacts,
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,22 +248,38 @@
|
|||||||
:key="profile.issuerDid"
|
:key="profile.issuerDid"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@click="onClickLoadItem(profile.issuerDid)"
|
@click="onClickLoadItem(profile?.rowId || '')"
|
||||||
class="block py-4 flex gap-4 cursor-pointer"
|
class="block py-4 flex gap-4 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||||
{{
|
{{
|
||||||
didInfo(profile.issuerDid, activeDid, allMyDids, allContacts)
|
didInfo(
|
||||||
|
profile.issuerDid,
|
||||||
|
activeDid,
|
||||||
|
allMyDids,
|
||||||
|
allContacts,
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<p v-if="profile.description" class="mt-1 text-sm text-slate-600">
|
<p
|
||||||
|
v-if="profile.description"
|
||||||
|
class="mt-1 text-sm text-slate-600"
|
||||||
|
>
|
||||||
{{ profile.description }}
|
{{ profile.description }}
|
||||||
</p>
|
</p>
|
||||||
<div v-if="profile.locLat && profile.locLon" class="mt-1 text-xs text-slate-500">
|
<div
|
||||||
|
v-if="isAnywhereActive && profile.locLat && profile.locLon"
|
||||||
|
class="mt-1 text-xs text-slate-500"
|
||||||
|
>
|
||||||
<fa icon="location-dot" class="fa-fw"></fa>
|
<fa icon="location-dot" class="fa-fw"></fa>
|
||||||
{{ profile.locLat.toFixed(2) }}, {{ profile.locLon.toFixed(2) }}
|
{{
|
||||||
|
(profile.locLat > 0 ? "North" : "South") +
|
||||||
|
" in " +
|
||||||
|
(profile.locLon > 0 ? "Eastern" : "Western") +
|
||||||
|
" Hemisphere"
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -432,17 +456,19 @@ export default class DiscoverView extends Vue {
|
|||||||
const results = await response.json();
|
const results = await response.json();
|
||||||
|
|
||||||
if (this.isProjectsActive) {
|
if (this.isProjectsActive) {
|
||||||
|
this.userProfiles = [];
|
||||||
const plans: PlanData[] = results.data;
|
const plans: PlanData[] = results.data;
|
||||||
if (plans) {
|
if (plans) {
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const { name, description, handleId, image, issuerDid, rowid } = plan;
|
const { name, description, handleId, image, issuerDid, rowId } =
|
||||||
|
plan;
|
||||||
this.projects.push({
|
this.projects.push({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
handleId,
|
handleId,
|
||||||
image,
|
image,
|
||||||
issuerDid,
|
issuerDid,
|
||||||
rowid,
|
rowId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.remoteCount = this.projects.length;
|
this.remoteCount = this.projects.length;
|
||||||
@@ -450,6 +476,7 @@ export default class DiscoverView extends Vue {
|
|||||||
throw JSON.stringify(results);
|
throw JSON.stringify(results);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.projects = [];
|
||||||
const profiles: UserProfile[] = results.data;
|
const profiles: UserProfile[] = results.data;
|
||||||
if (profiles) {
|
if (profiles) {
|
||||||
this.userProfiles.push(...profiles);
|
this.userProfiles.push(...profiles);
|
||||||
@@ -468,7 +495,11 @@ export default class DiscoverView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Searching",
|
title: "Error Searching",
|
||||||
text: e.userMessage || "There was a problem retrieving " + (this.isProjectsActive ? "projects" : "profiles") + ".",
|
text:
|
||||||
|
e.userMessage ||
|
||||||
|
"There was a problem retrieving " +
|
||||||
|
(this.isProjectsActive ? "projects" : "profiles") +
|
||||||
|
".",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
@@ -530,17 +561,18 @@ export default class DiscoverView extends Vue {
|
|||||||
const results = await response.json();
|
const results = await response.json();
|
||||||
|
|
||||||
if (this.isProjectsActive) {
|
if (this.isProjectsActive) {
|
||||||
|
this.userProfiles = [];
|
||||||
if (results.data) {
|
if (results.data) {
|
||||||
if (beforeId) {
|
if (beforeId) {
|
||||||
const plans: PlanData[] = results.data;
|
const plans: PlanData[] = results.data;
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const { name, description, handleId, issuerDid, rowid } = plan;
|
const { name, description, handleId, issuerDid, rowId } = plan;
|
||||||
this.projects.push({
|
this.projects.push({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
handleId,
|
handleId,
|
||||||
issuerDid,
|
issuerDid,
|
||||||
rowid,
|
rowId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -551,6 +583,7 @@ export default class DiscoverView extends Vue {
|
|||||||
throw JSON.stringify(results);
|
throw JSON.stringify(results);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.projects = [];
|
||||||
const profiles: UserProfile[] = results.data;
|
const profiles: UserProfile[] = results.data;
|
||||||
if (profiles) {
|
if (profiles) {
|
||||||
this.userProfiles.push(...profiles);
|
this.userProfiles.push(...profiles);
|
||||||
@@ -567,7 +600,11 @@ export default class DiscoverView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: e.userMessage || "There was a problem retrieving " + (this.isProjectsActive ? "projects" : "profiles") + ".",
|
text:
|
||||||
|
e.userMessage ||
|
||||||
|
"There was a problem retrieving " +
|
||||||
|
(this.isProjectsActive ? "projects" : "profiles") +
|
||||||
|
".",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
@@ -585,16 +622,16 @@ export default class DiscoverView extends Vue {
|
|||||||
if (this.isProjectsActive && this.projects.length > 0) {
|
if (this.isProjectsActive && this.projects.length > 0) {
|
||||||
const latestProject = this.projects[this.projects.length - 1];
|
const latestProject = this.projects[this.projects.length - 1];
|
||||||
if (this.isLocalActive || this.isMappedActive) {
|
if (this.isLocalActive || this.isMappedActive) {
|
||||||
this.searchLocal(latestProject.rowid);
|
this.searchLocal(latestProject.rowId);
|
||||||
} else if (this.isAnywhereActive) {
|
} else if (this.isAnywhereActive) {
|
||||||
this.searchAll(latestProject.rowid);
|
this.searchAll(latestProject.rowId);
|
||||||
}
|
}
|
||||||
} else if (!this.isProjectsActive && this.userProfiles.length > 0) {
|
} else if (!this.isProjectsActive && this.userProfiles.length > 0) {
|
||||||
const latestProfile = this.userProfiles[this.userProfiles.length - 1];
|
const latestProfile = this.userProfiles[this.userProfiles.length - 1];
|
||||||
if (this.isLocalActive || this.isMappedActive) {
|
if (this.isLocalActive || this.isMappedActive) {
|
||||||
this.searchLocal(latestProfile.rowid || "");
|
this.searchLocal(latestProfile.rowId || "");
|
||||||
} else if (this.isAnywhereActive) {
|
} else if (this.isAnywhereActive) {
|
||||||
this.searchAll(latestProfile.rowid || "");
|
this.searchAll(latestProfile.rowId || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,7 +690,7 @@ export default class DiscoverView extends Vue {
|
|||||||
this.markers = {};
|
this.markers = {};
|
||||||
const results = await response.json();
|
const results = await response.json();
|
||||||
if (results.data?.tiles?.length > 0) {
|
if (results.data?.tiles?.length > 0) {
|
||||||
for (const tile of results.data.tiles) {
|
for (const tile: Tile of results.data.tiles) {
|
||||||
const pinLat = (tile.minFoundLat + tile.maxFoundLat) / 2;
|
const pinLat = (tile.minFoundLat + tile.maxFoundLat) / 2;
|
||||||
const pinLon = (tile.minFoundLon + tile.maxFoundLon) / 2;
|
const pinLon = (tile.minFoundLon + tile.maxFoundLon) / 2;
|
||||||
const numberIcon = L.divIcon({
|
const numberIcon = L.divIcon({
|
||||||
@@ -711,7 +748,7 @@ export default class DiscoverView extends Vue {
|
|||||||
**/
|
**/
|
||||||
onClickLoadItem(id: string) {
|
onClickLoadItem(id: string) {
|
||||||
const route = {
|
const route = {
|
||||||
path: this.isProjectsActive
|
path: this.isProjectsActive
|
||||||
? "/project/" + encodeURIComponent(id)
|
? "/project/" + encodeURIComponent(id)
|
||||||
: "/userProfile/" + encodeURIComponent(id),
|
: "/userProfile/" + encodeURIComponent(id),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -361,14 +361,14 @@ export default class ProjectsView extends Vue {
|
|||||||
if (resp.status === 200 && resp.data.data) {
|
if (resp.status === 200 && resp.data.data) {
|
||||||
const plans: PlanData[] = resp.data.data;
|
const plans: PlanData[] = resp.data.data;
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const { name, description, handleId, image, issuerDid, rowid } = plan;
|
const { name, description, handleId, image, issuerDid, rowId } = plan;
|
||||||
this.projects.push({
|
this.projects.push({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
image,
|
image,
|
||||||
handleId,
|
handleId,
|
||||||
issuerDid,
|
issuerDid,
|
||||||
rowid,
|
rowId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -395,7 +395,7 @@ export default class ProjectsView extends Vue {
|
|||||||
async loadMoreProjectData(payload: boolean) {
|
async loadMoreProjectData(payload: boolean) {
|
||||||
if (this.projects.length > 0 && payload) {
|
if (this.projects.length > 0 && payload) {
|
||||||
const latestProject = this.projects[this.projects.length - 1];
|
const latestProject = this.projects[this.projects.length - 1];
|
||||||
await this.loadProjects(`beforeId=${latestProject.rowid}`);
|
await this.loadProjects(`beforeId=${latestProject.rowId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
151
src/views/UserProfileView.vue
Normal file
151
src/views/UserProfileView.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav selected="Discover" />
|
||||||
|
<TopMessage />
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light">
|
||||||
|
Individual Profile
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Loading Animation -->
|
||||||
|
<div
|
||||||
|
class="fixed left-6 mt-16 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
|
v-if="isLoading"
|
||||||
|
>
|
||||||
|
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="profile">
|
||||||
|
<!-- Profile Info -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<div class="text-sm">
|
||||||
|
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||||
|
{{ didInfo(profile.issuerDid, activeDid, allMyDids, allContacts) }}
|
||||||
|
</div>
|
||||||
|
<p v-if="profile.description" class="mt-4 text-slate-600">
|
||||||
|
{{ profile.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Map -->
|
||||||
|
<div v-if="profile?.locLat && profile?.locLon" class="mt-4">
|
||||||
|
<h2 class="text-lg font-semibold">Location</h2>
|
||||||
|
<div class="h-96 mt-2 w-full">
|
||||||
|
<l-map
|
||||||
|
ref="profileMap"
|
||||||
|
:center="[profile.locLat, profile.locLon]"
|
||||||
|
:zoom="12"
|
||||||
|
>
|
||||||
|
<l-tile-layer
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
layer-type="base"
|
||||||
|
name="OpenStreetMap"
|
||||||
|
/>
|
||||||
|
<l-marker :lat-lng="[profile.locLat, profile.locLon]">
|
||||||
|
<l-popup>{{
|
||||||
|
didInfo(profile.issuerDid, activeDid, allMyDids, allContacts)
|
||||||
|
}}</l-popup>
|
||||||
|
</l-marker>
|
||||||
|
</l-map>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center mt-8">
|
||||||
|
<p class="text-lg text-slate-500">Profile not found.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import "leaflet/dist/leaflet.css";
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { LMap, LTileLayer, LMarker, LPopup } from "@vue-leaflet/vue-leaflet";
|
||||||
|
import { Router, RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { DEFAULT_PARTNER_API_SERVER, NotificationIface } from "@/constants/app";
|
||||||
|
import { db } from "@/db/index";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
import { didInfo, getHeaders } from "@/libs/endorserServer";
|
||||||
|
import { UserProfile } from "@/libs/partnerServer";
|
||||||
|
import { retrieveAccountDids } from "@/libs/util";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
LMap,
|
||||||
|
LMarker,
|
||||||
|
LPopup,
|
||||||
|
LTileLayer,
|
||||||
|
QuickNav,
|
||||||
|
TopMessage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class UserProfileView extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
|
||||||
|
activeDid = "";
|
||||||
|
allContacts: Array<Contact> = [];
|
||||||
|
allMyDids: Array<string> = [];
|
||||||
|
isLoading = true;
|
||||||
|
partnerApiServer = DEFAULT_PARTNER_API_SERVER;
|
||||||
|
profile: UserProfile | null = null;
|
||||||
|
|
||||||
|
// make this function available to the Vue template
|
||||||
|
didInfo = didInfo;
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
const settings = await db.settings.toArray();
|
||||||
|
this.activeDid = settings[0]?.activeDid || "";
|
||||||
|
this.partnerApiServer =
|
||||||
|
settings[0]?.partnerApiServer || this.partnerApiServer;
|
||||||
|
|
||||||
|
this.allContacts = await db.contacts.toArray();
|
||||||
|
this.allMyDids = await retrieveAccountDids();
|
||||||
|
|
||||||
|
await this.loadProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadProfile() {
|
||||||
|
const profileId: string = this.$route.params.id as string;
|
||||||
|
if (!profileId) {
|
||||||
|
this.isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfile/${encodeURIComponent(profileId)}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: await getHeaders(this.activeDid),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const result = await response.json();
|
||||||
|
this.profile = result.data;
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to load profile");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading profile:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "There was a problem loading the profile.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user