24 changed files with 28862 additions and 27539 deletions
File diff suppressed because it is too large
@ -0,0 +1,182 @@ |
|||
<template> |
|||
<div |
|||
v-if="isOpen" |
|||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" |
|||
> |
|||
<div class="bg-white rounded-lg p-6 max-w-2xl w-full mx-4"> |
|||
<!-- Header --> |
|||
<div class="flex justify-between items-center mb-4"> |
|||
<h2 class="text-xl font-bold capitalize">{{ roleName }} Details</h2> |
|||
<button @click="close" class="text-gray-500 hover:text-gray-700"> |
|||
<fa icon="times" /> |
|||
</button> |
|||
</div> |
|||
|
|||
<!-- Content --> |
|||
<!-- This is somewhat similar to ClaimView.vue and ConfirmGiftView.vue --> |
|||
<div class="mb-4"> |
|||
<p class="mb-4"> |
|||
<span v-if="R.isEmpty(visibleToDids)"> |
|||
The {{ roleName }} is not visible to you or any of your contacts. |
|||
</span> |
|||
<span v-else> The {{ roleName }} is not visible to you. </span> |
|||
</p> |
|||
|
|||
<div v-if="R.isEmpty(visibleToDids)"> |
|||
<p class="mt-2"> |
|||
You can ask one of your contacts to take a look and see if their |
|||
contacts can see more details. Someone is connected to people closer |
|||
to them; if you don't know who to ask, try the person who registered |
|||
you. |
|||
</p> |
|||
</div> |
|||
|
|||
<div v-else> |
|||
<p class="mb-2"> |
|||
They are visible to some of your contacts. If you'd like an |
|||
introduction, ask them if they'll tell you more. |
|||
</p> |
|||
|
|||
<div class="ml-4"> |
|||
<ul> |
|||
<li |
|||
v-for="(visDid, idx) of visibleToDids" |
|||
:key="idx" |
|||
class="list-disc ml-4 mb-2" |
|||
> |
|||
<div class="text-sm"> |
|||
<span> |
|||
{{ didInfo(visDid) }} |
|||
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)"> |
|||
<a |
|||
:href="`/did/${visDid}`" |
|||
target="_blank" |
|||
class="text-blue-500" |
|||
> |
|||
<fa icon="arrow-up-right-from-square" class="fa-fw" /> |
|||
</a> |
|||
</span> |
|||
</span> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mt-4"> |
|||
<span v-if="canShare"> |
|||
If you'd like an introduction, |
|||
<a @click="onClickShareClaim()" class="text-blue-500" |
|||
>click here to share the information with them and ask if they'll |
|||
tell you more about the {{ roleName }}.</a |
|||
> |
|||
</span> |
|||
<span v-else> |
|||
If you'd like an introduction, |
|||
<a |
|||
@click="copyToClipboard('A link to this page', windowLocation)" |
|||
class="text-blue-500" |
|||
>click here to copy this page, paste it into a message, and ask if |
|||
they'll tell you more about the {{ roleName }}.</a |
|||
> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Footer --> |
|||
<div class="flex justify-end"> |
|||
<button |
|||
@click="close" |
|||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" |
|||
> |
|||
Close |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Vue } from "vue-facing-decorator"; |
|||
import * as R from "ramda"; |
|||
import { useClipboard } from "@vueuse/core"; |
|||
import { Contact } from "@/db/tables/contacts"; |
|||
import * as serverUtil from "@/libs/endorserServer"; |
|||
import { NotificationIface } from "@/constants/app"; |
|||
|
|||
@Component |
|||
export default class HiddenDidDialog extends Vue { |
|||
$notify!: (notification: NotificationIface, timeout?: number) => void; |
|||
|
|||
isOpen = false; |
|||
roleName = ""; |
|||
visibleToDids: string[] = []; |
|||
allContacts: Array<Contact> = []; |
|||
activeDid = ""; |
|||
allMyDids: Array<string> = []; |
|||
canShare = false; |
|||
windowLocation = window.location.href; |
|||
|
|||
R = R; |
|||
serverUtil = serverUtil; |
|||
|
|||
created() { |
|||
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare |
|||
// then use this truer check: navigator.canShare && navigator.canShare() |
|||
this.canShare = !!navigator.share; |
|||
} |
|||
|
|||
open( |
|||
roleName: string, |
|||
visibleToDids: string[], |
|||
allContacts: Array<Contact>, |
|||
activeDid: string, |
|||
allMyDids: Array<string>, |
|||
) { |
|||
this.roleName = roleName; |
|||
this.visibleToDids = visibleToDids; |
|||
this.allContacts = allContacts; |
|||
this.activeDid = activeDid; |
|||
this.allMyDids = allMyDids; |
|||
this.isOpen = true; |
|||
} |
|||
|
|||
close() { |
|||
this.isOpen = false; |
|||
} |
|||
|
|||
didInfo(did: string) { |
|||
return serverUtil.didInfo( |
|||
did, |
|||
this.activeDid, |
|||
this.allMyDids, |
|||
this.allContacts, |
|||
); |
|||
} |
|||
|
|||
copyToClipboard(name: string, text: string) { |
|||
useClipboard() |
|||
.copy(text) |
|||
.then(() => { |
|||
this.$notify( |
|||
{ |
|||
group: "alert", |
|||
type: "toast", |
|||
title: "Copied", |
|||
text: (name || "That") + " was copied to the clipboard.", |
|||
}, |
|||
2000, |
|||
); |
|||
}); |
|||
} |
|||
|
|||
onClickShareClaim() { |
|||
this.copyToClipboard("A link to this page", this.windowLocation); |
|||
window.navigator.share({ |
|||
title: "Help Connect Me", |
|||
text: "I'm trying to find the people who recorded this. Can you help me?", |
|||
url: this.windowLocation, |
|||
}); |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,9 @@ |
|||
export interface UserProfile { |
|||
description: string; |
|||
locLat?: number; |
|||
locLon?: number; |
|||
locLat2?: number; |
|||
locLon2?: number; |
|||
issuerDid: string; |
|||
rowId?: string; // set on profile retrieved from server
|
|||
} |
@ -0,0 +1,184 @@ |
|||
<template> |
|||
<QuickNav selected="Discover" /> |
|||
<TopMessage /> |
|||
|
|||
<!-- CONTENT --> |
|||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> |
|||
<!-- Breadcrumb --> |
|||
<div id="ViewBreadcrumb" class="mb-8"> |
|||
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7"> |
|||
<!-- Back --> |
|||
<button |
|||
@click="$router.go(-1)" |
|||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" |
|||
> |
|||
<fa icon="chevron-left" class="fa-fw"></fa> |
|||
</button> |
|||
Individual Profile |
|||
</h1> |
|||
</div> |
|||
|
|||
<!-- 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 for first coordinates --> |
|||
<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> |
|||
|
|||
<!-- Map for second coordinates --> |
|||
<div v-if="profile?.locLat2 && profile?.locLon2" class="mt-4"> |
|||
<h2 class="text-lg font-semibold">Second Location</h2> |
|||
<div class="h-96 mt-2 w-full"> |
|||
<l-map |
|||
ref="profileMap" |
|||
:center="[profile.locLat2, profile.locLon2]" |
|||
: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.locLat2, profile.locLon2]"> |
|||
<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> |
Loading…
Reference in new issue