24 changed files with 28862 additions and 27539 deletions
			
			
		
								
									
										File diff suppressed because it is too large
									
								
							
						
					| @ -1,141 +1,133 @@ | |||
| { | |||
|     "name": "TimeSafari", | |||
|     "version": "0.3.51-beta", | |||
|     "description": "A cross-platform app for managing time-based crowdfunding.", | |||
|     "author": "Your Name <your.email@example.com>", | |||
|     "main": "src/electron/main.js", | |||
|     "scripts": { | |||
|         "dev": "vite", | |||
|         "serve": "vite preview", | |||
|         "build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build", | |||
|         "build:capacitor": "vite build --mode capacitor", | |||
|         "build:electron": "vite build --mode electron", | |||
|         "electron:dev": "vite build --mode electron && electron .", | |||
|         "electron:build": "electron-builder", | |||
|         "capacitor:sync": "npx cap copy", | |||
|         "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", | |||
|         "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", | |||
|         "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js", | |||
|         "test-local": "npx playwright test -c playwright.config-local.ts --trace on", | |||
|         "test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on" | |||
|   "name": "TimeSafari", | |||
|   "version": "0.3.54-beta", | |||
|   "scripts": { | |||
|     "dev": "vite", | |||
|     "serve": "vite preview", | |||
|     "build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build", | |||
|     "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", | |||
|     "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", | |||
|     "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js", | |||
|     "test-local": "npx playwright test -c playwright.config-local.ts --trace on", | |||
|     "test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on" | |||
|   }, | |||
|   "dependencies": { | |||
|     "@capacitor/android": "^6.2.0", | |||
|     "@capacitor/cli": "^6.2.0", | |||
|     "@capacitor/core": "^6.2.0", | |||
|     "@capacitor/ios": "^6.2.0", | |||
|     "@dicebear/collection": "^5.4.1", | |||
|     "@dicebear/core": "^5.4.1", | |||
|     "@ethersproject/hdnode": "^5.7.0", | |||
|     "@fortawesome/fontawesome-svg-core": "^6.5.1", | |||
|     "@fortawesome/free-solid-svg-icons": "^6.5.1", | |||
|     "@fortawesome/vue-fontawesome": "^3.0.6", | |||
|     "@peculiar/asn1-ecc": "^2.3.8", | |||
|     "@peculiar/asn1-schema": "^2.3.8", | |||
|     "@pvermeer/dexie-encrypted-addon": "^3.0.0", | |||
|     "@simplewebauthn/browser": "^10.0.0", | |||
|     "@simplewebauthn/server": "^10.0.0", | |||
|     "@tweenjs/tween.js": "^21.1.1", | |||
|     "@types/qrcode": "^1.5.5", | |||
|     "@veramo/core": "^5.6.0", | |||
|     "@veramo/credential-w3c": "^5.6.0", | |||
|     "@veramo/data-store": "^5.6.0", | |||
|     "@veramo/did-manager": "^5.6.0", | |||
|     "@veramo/did-provider-ethr": "^5.6.0", | |||
|     "@veramo/did-provider-peer": "^6.0.0", | |||
|     "@veramo/did-resolver": "^5.6.0", | |||
|     "@veramo/key-manager": "^5.6.0", | |||
|     "@vue-leaflet/vue-leaflet": "^0.10.1", | |||
|     "@vueuse/core": "^12.3.0", | |||
|     "@zxing/text-encoding": "^0.9.0", | |||
|     "asn1-ber": "^1.2.2", | |||
|     "axios": "^1.6.8", | |||
|     "cbor-x": "^1.5.9", | |||
|     "class-transformer": "^0.5.1", | |||
|     "dexie": "^3.2.7", | |||
|     "dexie-export-import": "^4.1.1", | |||
|     "did-jwt": "^7.4.7", | |||
|     "did-resolver": "^4.1.0", | |||
|     "ethereum-cryptography": "^2.1.3", | |||
|     "ethereumjs-util": "^7.1.5", | |||
|     "jdenticon": "^3.2.0", | |||
|     "js-generate-password": "^0.1.9", | |||
|     "js-yaml": "^4.1.0", | |||
|     "leaflet": "^1.9.4", | |||
|     "localstorage-slim": "^2.7.0", | |||
|     "lru-cache": "^10.2.0", | |||
|     "luxon": "^3.4.4", | |||
|     "merkletreejs": "^0.3.11", | |||
|     "nostr-tools": "^2.7.2", | |||
|     "notiwind": "^2.0.2", | |||
|     "papaparse": "^5.4.1", | |||
|     "pina": "^0.20.2204228", | |||
|     "pinia-plugin-persistedstate": "^3.2.1", | |||
|     "qr-code-generator-vue3": "^1.4.21", | |||
|     "qrcode": "^1.5.4", | |||
|     "ramda": "^0.29.1", | |||
|     "readable-stream": "^4.5.2", | |||
|     "reflect-metadata": "^0.1.14", | |||
|     "register-service-worker": "^1.7.2", | |||
|     "simple-vue-camera": "^1.1.3", | |||
|     "three": "^0.156.1", | |||
|     "ua-parser-js": "^1.0.37", | |||
|     "util": "^0.12.5", | |||
|     "vue": "^3.5.13", | |||
|     "vue-axios": "^3.5.2", | |||
|     "vue-facing-decorator": "^3.0.4", | |||
|     "vue-picture-cropper": "^0.7.0", | |||
|     "vue-qrcode-reader": "^5.5.3", | |||
|     "vue-router": "^4.5.0", | |||
|     "web-did-resolver": "^2.0.27" | |||
|   }, | |||
|   "devDependencies": { | |||
|     "@playwright/test": "^1.45.2", | |||
|     "@types/js-yaml": "^4.0.9", | |||
|     "@types/leaflet": "^1.9.8", | |||
|     "@types/luxon": "^3.4.2", | |||
|     "@types/node": "^20.14.11", | |||
|     "@types/ramda": "^0.29.11", | |||
|     "@types/three": "^0.155.1", | |||
|     "@types/ua-parser-js": "^0.7.39", | |||
|     "@typescript-eslint/eslint-plugin": "^6.21.0", | |||
|     "@typescript-eslint/parser": "^6.21.0", | |||
|     "@vitejs/plugin-vue": "^5.2.1", | |||
|     "@vue/eslint-config-typescript": "^11.0.3", | |||
|     "autoprefixer": "^10.4.19", | |||
|     "electron": "^33.2.1", | |||
|     "electron-builder": "^25.1.8", | |||
|     "eslint": "^8.57.0", | |||
|     "eslint-config-prettier": "^9.1.0", | |||
|     "eslint-plugin-prettier": "^5.2.1", | |||
|     "eslint-plugin-vue": "^9.32.0", | |||
|     "npm-check-updates": "^17.1.13", | |||
|     "postcss": "^8.4.38", | |||
|     "prettier": "^3.2.5", | |||
|     "tailwindcss": "^3.4.1", | |||
|     "typescript": "~5.2.2", | |||
|     "vite": "^5.2.0", | |||
|     "vite-plugin-pwa": "^0.19.8" | |||
|   }, | |||
|   "build": { | |||
|     "appId": "com.example.app", | |||
|     "productName": "TimeSafari", | |||
|     "directories": { | |||
|       "output": "dist-electron-build" | |||
|     }, | |||
|     "dependencies": { | |||
|         "@capacitor/android": "^6.2.0", | |||
|         "@capacitor/cli": "^6.2.0", | |||
|         "@capacitor/core": "^6.2.0", | |||
|         "@capacitor/ios": "^6.2.0", | |||
|         "@dicebear/collection": "^5.4.1", | |||
|         "@dicebear/core": "^5.4.1", | |||
|         "@ethersproject/hdnode": "^5.7.0", | |||
|         "@fortawesome/fontawesome-svg-core": "^6.5.1", | |||
|         "@fortawesome/free-solid-svg-icons": "^6.5.1", | |||
|         "@fortawesome/vue-fontawesome": "^3.0.6", | |||
|         "@peculiar/asn1-ecc": "^2.3.8", | |||
|         "@peculiar/asn1-schema": "^2.3.8", | |||
|         "@pvermeer/dexie-encrypted-addon": "^3.0.0", | |||
|         "@simplewebauthn/browser": "^10.0.0", | |||
|         "@simplewebauthn/server": "^10.0.0", | |||
|         "@tweenjs/tween.js": "^21.1.1", | |||
|         "@types/qrcode": "^1.5.5", | |||
|         "@veramo/core": "^5.6.0", | |||
|         "@veramo/credential-w3c": "^5.6.0", | |||
|         "@veramo/data-store": "^5.6.0", | |||
|         "@veramo/did-manager": "^5.6.0", | |||
|         "@veramo/did-provider-ethr": "^5.6.0", | |||
|         "@veramo/did-provider-peer": "^6.0.0", | |||
|         "@veramo/did-resolver": "^5.6.0", | |||
|         "@veramo/key-manager": "^5.6.0", | |||
|         "@vue-leaflet/vue-leaflet": "^0.10.1", | |||
|         "@vueuse/core": "^12.3.0", | |||
|         "@zxing/text-encoding": "^0.9.0", | |||
|         "asn1-ber": "^1.2.2", | |||
|         "axios": "^1.6.8", | |||
|         "cbor-x": "^1.5.9", | |||
|         "class-transformer": "^0.5.1", | |||
|         "dexie": "^3.2.7", | |||
|         "dexie-export-import": "^4.1.1", | |||
|         "did-jwt": "^7.4.7", | |||
|         "did-resolver": "^4.1.0", | |||
|         "ethereum-cryptography": "^2.1.3", | |||
|         "ethereumjs-util": "^7.1.5", | |||
|         "jdenticon": "^3.2.0", | |||
|         "js-generate-password": "^0.1.9", | |||
|         "js-yaml": "^4.1.0", | |||
|         "leaflet": "^1.9.4", | |||
|         "localstorage-slim": "^2.7.0", | |||
|         "lru-cache": "^10.2.0", | |||
|         "luxon": "^3.4.4", | |||
|         "merkletreejs": "^0.3.11", | |||
|         "nostr-tools": "^2.7.2", | |||
|         "notiwind": "^2.0.2", | |||
|         "papaparse": "^5.4.1", | |||
|         "pina": "^0.20.2204228", | |||
|         "pinia-plugin-persistedstate": "^3.2.1", | |||
|         "qr-code-generator-vue3": "^1.4.21", | |||
|         "qrcode": "^1.5.4", | |||
|         "ramda": "^0.29.1", | |||
|         "readable-stream": "^4.5.2", | |||
|         "reflect-metadata": "^0.1.14", | |||
|         "register-service-worker": "^1.7.2", | |||
|         "simple-vue-camera": "^1.1.3", | |||
|         "three": "^0.156.1", | |||
|         "ua-parser-js": "^1.0.37", | |||
|         "util": "^0.12.5", | |||
|         "vue": "^3.5.13", | |||
|         "vue-axios": "^3.5.2", | |||
|         "vue-facing-decorator": "^3.0.4", | |||
|         "vue-picture-cropper": "^0.7.0", | |||
|         "vue-qrcode-reader": "^5.5.3", | |||
|         "vue-router": "^4.5.0", | |||
|         "web-did-resolver": "^2.0.27" | |||
|     "files": [ | |||
|       "dist-electron/**", | |||
|       "src/electron/**" | |||
|     ], | |||
|     "mac": { | |||
|       "target": "dmg" | |||
|     }, | |||
|     "devDependencies": { | |||
|         "@playwright/test": "^1.45.2", | |||
|         "@types/js-yaml": "^4.0.9", | |||
|         "@types/leaflet": "^1.9.8", | |||
|         "@types/luxon": "^3.4.2", | |||
|         "@types/node": "^20.14.11", | |||
|         "@types/ramda": "^0.29.11", | |||
|         "@types/three": "^0.155.1", | |||
|         "@types/ua-parser-js": "^0.7.39", | |||
|         "@typescript-eslint/eslint-plugin": "^6.21.0", | |||
|         "@typescript-eslint/parser": "^6.21.0", | |||
|         "@vitejs/plugin-vue": "^5.2.1", | |||
|         "@vue/eslint-config-typescript": "^11.0.0", | |||
|         "autoprefixer": "^10.4.19", | |||
|         "electron": "^33.2.1", | |||
|         "electron-builder": "^25.1.8", | |||
|         "eslint": "^8.0.0", | |||
|         "eslint-config-prettier": "^9.1.0", | |||
|         "eslint-plugin-prettier": "^5.2.1", | |||
|         "eslint-plugin-vue": "^9.32.0", | |||
|         "npm-check-updates": "^17.1.13", | |||
|         "postcss": "^8.4.38", | |||
|         "prettier": "^3.2.5", | |||
|         "tailwindcss": "^3.4.1", | |||
|         "typescript": "~5.2.2", | |||
|         "vite": "^6.0.7", | |||
|         "vite-plugin-pwa": "^0.21.1" | |||
|     "win": { | |||
|       "target": "nsis" | |||
|     }, | |||
|     "build": { | |||
|         "appId": "com.example.app", | |||
|         "productName": "TimeSafari", | |||
|         "directories": { | |||
|             "output": "dist-electron-build" | |||
|         }, | |||
|         "files": [ | |||
|             "dist-electron/**", | |||
|             "src/electron/**" | |||
|         ], | |||
|         "mac": { | |||
|             "target": "dmg" | |||
|         }, | |||
|         "win": { | |||
|             "target": "nsis" | |||
|         }, | |||
|         "linux": { | |||
|             "target": "AppImage" | |||
|         }, | |||
| 	"asar": false | |||
|     } | |||
| } | |||
|     "linux": { | |||
|       "target": "AppImage" | |||
|     }, | |||
|     "asar": false | |||
|   } | |||
| } | |||
| @ -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