forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
14 Commits
adjust-not
...
sw-cleanup
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f0d10d93d | |||
|
|
a60beb483c | ||
| 6045975b79 | |||
| a6bb036ceb | |||
| 1e2ad85547 | |||
|
|
3e2723b744 | ||
| 4daffe8f40 | |||
| efb1922826 | |||
| c6e10bfdad | |||
| bb122be319 | |||
| 3f436476a2 | |||
| a77d20b572 | |||
| 69a25ddd6c | |||
|
|
9846cf3e4c |
@@ -26,6 +26,11 @@ If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js,
|
||||
npm run build
|
||||
```
|
||||
|
||||
```
|
||||
npx prettier --write ./sw_scripts/
|
||||
```
|
||||
to make sure the service worker scripts are in proper form
|
||||
|
||||
... then copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
|
||||
|
||||
|
||||
@@ -59,6 +64,10 @@ Under the "Your Identity" screen, click "Advanced", click "Switch Identity / No
|
||||
|
||||
For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain.
|
||||
|
||||
### Icons
|
||||
|
||||
To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name.
|
||||
|
||||
### Manual walk-through
|
||||
|
||||
- Clear the browser cache for localhost for a new user.
|
||||
|
||||
@@ -6,19 +6,40 @@ tasks:
|
||||
- extract private_key_hex in py-push-server webpush.py
|
||||
- lock down regenerate_vapid endpoint (so only we admins can do it on demand)
|
||||
- remove sleep in py-push-server app.py
|
||||
- revisit "maybe" and "never" buttons on accont screen
|
||||
- see if we can detect OS-level notifications if turned off
|
||||
- write troubleshooting docs for notifications
|
||||
|
||||
- .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
||||
|
||||
- .5 Add infinite scroll to gifts on the home page
|
||||
|
||||
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
||||
- .5 If notifications are not enabled, add message to front page with link/button to enable
|
||||
|
||||
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
|
||||
- .5 make a VC details page, or link to endorser.ch (including confirmations)
|
||||
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
|
||||
- show VC details... somehow:
|
||||
- .5 make a VC details page, or link to endorser.ch (including confirmations)
|
||||
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
|
||||
- 04 allow user to download VCs, mine + ones I can see about me from others
|
||||
- add VC confirmation?
|
||||
|
||||
- Release Minimum Viable Product :
|
||||
- generate new webpush.db entry, webpush.py private_key_hex & subscription_info & vapid_claims email
|
||||
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
|
||||
- 08 thorough testing for errors & edge cases
|
||||
- 01 ensure ability to recover server remotely, and add redundant access
|
||||
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).
|
||||
- Add disclaimers.
|
||||
- Switch default server to the public server.
|
||||
- Deploy to a server.
|
||||
- Ensure public server has limits that work for group adoption.
|
||||
- Test PWA features on Android and iOS.
|
||||
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
||||
|
||||
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
|
||||
- allow some gives even if they aren't registered
|
||||
- .5 Add start date to project
|
||||
- .3 check that Android shows "back" buttons on screens without bottom tray
|
||||
- .1 Make give description text box into something that expands as they type?
|
||||
- 04 allow user to download claims, mine + ones I can see about me from others
|
||||
- .5 customize favicon assignee-group:ui
|
||||
- .2 Show a warning if both giver and recipient are the same (but still allow?)
|
||||
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
|
||||
@@ -26,8 +47,6 @@ tasks:
|
||||
- .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch)
|
||||
- .5 remove references to localStorage for projectId (now that it's pulling from the path)
|
||||
- bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent
|
||||
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
|
||||
- allow some gives even if they aren't registered
|
||||
- switch some checks for activeDid to check for isRegistered
|
||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
|
||||
@@ -43,19 +62,6 @@ tasks:
|
||||
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
|
||||
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
||||
|
||||
- Release Minimum Viable Product :
|
||||
- generate new webpush.db entries, data/webpush.db private_key_hex & subscription_info & vapid_claims email
|
||||
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
|
||||
- 08 thorough testing for errors & edge cases
|
||||
- 01 ensure ability to recover server remotely, and add redundant access
|
||||
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).
|
||||
- Add disclaimers.
|
||||
- Switch default server to the public server.
|
||||
- Deploy to a server.
|
||||
- Ensure public server has limits that work for group adoption.
|
||||
- Test PWA features on Android and iOS.
|
||||
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
||||
|
||||
- .5 show seed phrase in a QR code for transfer to another device
|
||||
- .5 on DiscoverView, switch to a filter UI (eg. just from friend
|
||||
- .5 don't show "Offer" on project screen if they aren't registered
|
||||
|
||||
@@ -426,16 +426,13 @@ export default class App extends Vue {
|
||||
this.subscribeToPush()
|
||||
.then(() => {
|
||||
console.log("Subscribed successfully.");
|
||||
// Assuming the subscription object is available
|
||||
return navigator.serviceWorker.ready;
|
||||
})
|
||||
.then((registration) => {
|
||||
// Fetch the existing subscription object from the registration
|
||||
return registration.pushManager.getSubscription();
|
||||
})
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
console.log(subscription);
|
||||
return this.sendSubscriptionToServer(subscription);
|
||||
} else {
|
||||
throw new Error("Subscription object is not available.");
|
||||
@@ -449,15 +446,16 @@ export default class App extends Vue {
|
||||
"Subscription or server communication failed:",
|
||||
error,
|
||||
);
|
||||
alert( "Subscription or server communication failed. Try again in a while." );
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("An error occurred:", error);
|
||||
// Handle error appropriately here
|
||||
alert( "Some error occurred." + error );
|
||||
});
|
||||
}
|
||||
|
||||
// Function to convert URL base64 to Uint8Array
|
||||
|
||||
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
|
||||
@@ -69,6 +69,8 @@ export interface OfferServerRecord {
|
||||
validThrough: string;
|
||||
}
|
||||
|
||||
// Note that previous VCs may have additional fields.
|
||||
// https://endorser.ch/doc/html/transactions.html#id4
|
||||
export interface GiveVerifiableCredential {
|
||||
"@context"?: string; // optional when embedded, eg. in an Agree
|
||||
"@type": "GiveAction";
|
||||
@@ -80,6 +82,8 @@ export interface GiveVerifiableCredential {
|
||||
recipient?: { identifier: string };
|
||||
}
|
||||
|
||||
// Note that previous VCs may have additional fields.
|
||||
// https://endorser.ch/doc/html/transactions.html#id8
|
||||
export interface OfferVerifiableCredential {
|
||||
"@context"?: string; // optional when embedded, eg. in an Agree
|
||||
"@type": "Offer";
|
||||
@@ -93,6 +97,8 @@ export interface OfferVerifiableCredential {
|
||||
validThrough?: string;
|
||||
}
|
||||
|
||||
// Note that previous VCs may have additional fields.
|
||||
// https://endorser.ch/doc/html/transactions.html#id7
|
||||
export interface PlanVerifiableCredential {
|
||||
"@context": "https://schema.org";
|
||||
"@type": "PlanAction";
|
||||
@@ -157,8 +163,8 @@ export function didInfo(
|
||||
return contact
|
||||
? contact.name || "Contact With No Name"
|
||||
: isHiddenDid(did)
|
||||
? "Someone Not In Network"
|
||||
: "Someone Not In Contacts";
|
||||
? "Someone Not In Network"
|
||||
: "Someone Not In Contacts";
|
||||
}
|
||||
|
||||
export interface ResultWithType {
|
||||
|
||||
3
src/libs/util.ts
Normal file
3
src/libs/util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const isGlobalUri = (uri: string) => {
|
||||
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faChevronLeft,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faDollar,
|
||||
faEllipsisVertical,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
@@ -34,6 +36,7 @@ import {
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHand,
|
||||
faHouseChimney,
|
||||
faLocationDot,
|
||||
@@ -44,6 +47,7 @@ import {
|
||||
faPersonCircleCheck,
|
||||
faPersonCircleQuestion,
|
||||
faPlus,
|
||||
faQuestion,
|
||||
faQrcode,
|
||||
faRotate,
|
||||
faShareNodes,
|
||||
@@ -61,6 +65,7 @@ library.add(
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faChevronLeft,
|
||||
@@ -74,6 +79,7 @@ library.add(
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faDollar,
|
||||
faEllipsisVertical,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
@@ -81,6 +87,7 @@ library.add(
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHand,
|
||||
faHouseChimney,
|
||||
faLocationDot,
|
||||
@@ -92,6 +99,7 @@ library.add(
|
||||
faPersonCircleQuestion,
|
||||
faPlus,
|
||||
faQrcode,
|
||||
faQuestion,
|
||||
faRotate,
|
||||
faShareNodes,
|
||||
faSpinner,
|
||||
|
||||
@@ -91,6 +91,14 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "help" */ "../views/HelpView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/help-notifications",
|
||||
name: "help-notifications",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/identity-switcher",
|
||||
name: "identity-switcher",
|
||||
|
||||
@@ -105,7 +105,12 @@
|
||||
<!-- toggle -->
|
||||
<div class="relative ml-2">
|
||||
<!-- input -->
|
||||
<input type="checkbox" name="toggleNotifications" class="sr-only" />
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="toggleNotifications"
|
||||
name="toggleNotifications"
|
||||
class="sr-only"
|
||||
/>
|
||||
<!-- line -->
|
||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||
<!-- dot -->
|
||||
@@ -136,6 +141,7 @@
|
||||
type="checkbox"
|
||||
name="toggleMuteNotifications"
|
||||
class="sr-only"
|
||||
disabled
|
||||
/>
|
||||
<!-- line -->
|
||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||
@@ -288,6 +294,14 @@
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="flex py-2">
|
||||
<button class="text-blue-500">
|
||||
<router-link :to="{ name: 'statistics' }" class="block text-center">
|
||||
See Global Animated History of Giving
|
||||
</router-link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex py-2">
|
||||
<button class="text-blue-500">
|
||||
<!-- id used by puppeteer test script -->
|
||||
@@ -301,14 +315,6 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex py-2">
|
||||
<button class="text-blue-500">
|
||||
<router-link :to="{ name: 'statistics' }" class="block text-center">
|
||||
See Achievements & Statistics
|
||||
</router-link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex py-4">
|
||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
|
||||
<input
|
||||
@@ -447,6 +453,16 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
showAdvanced = false;
|
||||
|
||||
private isSubscribed = false;
|
||||
|
||||
get toggleNotifications() {
|
||||
return this.isSubscribed;
|
||||
}
|
||||
|
||||
set toggleNotifications(value) {
|
||||
this.isSubscribed = value;
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
|
||||
try {
|
||||
// Open the accounts database
|
||||
@@ -529,6 +545,7 @@ export default class AccountViewView extends Vue {
|
||||
* @throws Will display specific messages to the user based on different errors.
|
||||
*/
|
||||
async created() {
|
||||
console.error("created");
|
||||
try {
|
||||
await db.open();
|
||||
|
||||
@@ -547,6 +564,18 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async mounted() {
|
||||
console.error("mounted()");
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
this.toggleNotifications = !!subscription;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.toggleNotifications = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes component state with values from the database or defaults.
|
||||
* @param {SettingsType} settings - Object containing settings from the database.
|
||||
@@ -756,7 +785,7 @@ export default class AccountViewView extends Vue {
|
||||
});
|
||||
this.isRegistered = true;
|
||||
} catch (err) {
|
||||
console.log("Got an error updating settings:", err);
|
||||
console.error("Got an error updating settings:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -798,10 +827,9 @@ export default class AccountViewView extends Vue {
|
||||
const data = error.response?.data as ErrorResponse;
|
||||
this.limitsMessage =
|
||||
(data?.error?.message as string) || "Bad server response.";
|
||||
console.log(
|
||||
console.error(
|
||||
"Got bad response retrieving limits, which usually means user isn't registered. Server says:",
|
||||
this.limitsMessage,
|
||||
//error,
|
||||
);
|
||||
} else if (
|
||||
error instanceof Error &&
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
showGiveTotals
|
||||
? "Total"
|
||||
: showGiveConfirmed
|
||||
? "Confirmed"
|
||||
: "Unconfirmed"
|
||||
? "Confirmed"
|
||||
: "Unconfirmed"
|
||||
}}
|
||||
</button>
|
||||
<br />
|
||||
@@ -957,6 +957,7 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// similar function is in endorserServer.ts
|
||||
private async createAndSubmitGive(
|
||||
identity: IIdentifier,
|
||||
fromDid: string,
|
||||
|
||||
65
src/views/HelpNotificationsView.vue
Normal file
65
src/views/HelpNotificationsView.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<QuickNav />
|
||||
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="p-6 pb-24">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-8">
|
||||
<!-- Back -->
|
||||
<div class="text-lg text-center font-light relative px-7">
|
||||
<h1
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
@click="$router.back()"
|
||||
>
|
||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Heading -->
|
||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||
Notification Help
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Here are things to try to get notifications working.</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Test</h2>
|
||||
<p>Somehow call the service-worker self.showNotification</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
|
||||
<p>
|
||||
Walk-throughs & screenshots, maybe for all combinations of OS &
|
||||
browsers.
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
|
||||
<p>Walk-throughs & screenshots for browser settings</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
|
||||
<p>
|
||||
Walk-throughs for clearing everything & subscribing anew to get a
|
||||
message
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Auto-detection</h2>
|
||||
<p>Show results of auto-detection whether they're turned on</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
|
||||
interface Notification {
|
||||
group: string;
|
||||
type: string;
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Component({ components: { QuickNav } })
|
||||
export default class HelpNotificationsView extends Vue {
|
||||
$notify!: (notification: Notification, timeout?: number) => void;
|
||||
}
|
||||
</script>
|
||||
@@ -181,6 +181,21 @@
|
||||
different page.
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">
|
||||
How do I access even more functionality?
|
||||
</h2>
|
||||
<p>
|
||||
There is an "Advanced" section at the bottom of the Account
|
||||
<fa icon="circle-user" /> page.
|
||||
</p>
|
||||
<p>
|
||||
There is a even more functionality in a mobile app (and more
|
||||
documentation) at
|
||||
<a href="https://endorser.ch" class="text-blue-500">
|
||||
EndorserSearch.com
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">What is your privacy policy?</h2>
|
||||
<p>
|
||||
See
|
||||
|
||||
@@ -26,20 +26,26 @@
|
||||
type="text"
|
||||
placeholder="Idea Name"
|
||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||
v-model="projectName"
|
||||
v-model="fullClaim.name"
|
||||
/>
|
||||
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||
rows="5"
|
||||
v-model="description"
|
||||
v-model="fullClaim.description"
|
||||
maxlength="5000"
|
||||
></textarea>
|
||||
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
|
||||
{{ description.length }}/5000 max. characters
|
||||
{{ fullClaim.description.length }}/5000 max. characters
|
||||
</div>
|
||||
|
||||
<input
|
||||
v-model="fullClaim.url"
|
||||
placeholder="Website"
|
||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||
/>
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -72,7 +78,7 @@
|
||||
name="OpenStreetMap"
|
||||
/>
|
||||
<l-marker
|
||||
v-if="latitude || longitude"
|
||||
v-if="latitude && longitude"
|
||||
:lat-lng="[latitude, longitude]"
|
||||
@click="maybeEraseLatLong()"
|
||||
/>
|
||||
@@ -136,13 +142,17 @@ export default class NewEditProjectView extends Vue {
|
||||
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
description = "";
|
||||
errorMessage = "";
|
||||
fullClaim: PlanVerifiableCredential = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "PlanAction",
|
||||
name: "",
|
||||
description: "",
|
||||
}; // this default is only to avoid errors before plan is loaded
|
||||
includeLocation = false;
|
||||
latitude = 0;
|
||||
longitude = 0;
|
||||
numAccounts = 0;
|
||||
projectName = "";
|
||||
zoom = 2;
|
||||
|
||||
async beforeCreate() {
|
||||
@@ -214,9 +224,12 @@ export default class NewEditProjectView extends Vue {
|
||||
try {
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200) {
|
||||
const claim = resp.data.claim;
|
||||
this.projectName = claim.name;
|
||||
this.description = claim.description;
|
||||
this.fullClaim = resp.data.claim;
|
||||
if (this.fullClaim?.location) {
|
||||
this.includeLocation = true;
|
||||
this.latitude = this.fullClaim.location.geo.latitude;
|
||||
this.longitude = this.fullClaim.location.geo.longitude;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Got error retrieving that project", error);
|
||||
@@ -225,13 +238,7 @@ export default class NewEditProjectView extends Vue {
|
||||
|
||||
private async SaveProject(identity: IIdentifier) {
|
||||
// Make a claim
|
||||
const vcClaim: PlanVerifiableCredential = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "PlanAction",
|
||||
name: this.projectName,
|
||||
description: this.description,
|
||||
identifier: this.projectId || undefined,
|
||||
};
|
||||
const vcClaim: PlanVerifiableCredential = this.fullClaim;
|
||||
if (this.projectId) {
|
||||
vcClaim.identifier = this.projectId;
|
||||
}
|
||||
@@ -314,8 +321,8 @@ export default class NewEditProjectView extends Vue {
|
||||
error?: { message?: string };
|
||||
}>;
|
||||
if (serverError) {
|
||||
console.log("Got error from server", serverError);
|
||||
if (Object.prototype.hasOwnProperty.call(serverError, "message")) {
|
||||
console.log(serverError);
|
||||
userMessage = serverError.response?.data?.error?.message || ""; // This is info for the user.
|
||||
this.$notify(
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
{{ issuer }}
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="timeSince">
|
||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
||||
{{ timeSince }}
|
||||
</div>
|
||||
@@ -45,8 +45,13 @@
|
||||
:href="getOpenStreetMapUrl()"
|
||||
target="_blank"
|
||||
class="underline"
|
||||
>
|
||||
Map View
|
||||
>Map View
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="url">
|
||||
<fa icon="globe" class="fa-fw text-slate-400"></fa>
|
||||
<a :href="addScheme(url)" target="_blank" class="underline"
|
||||
>{{ domainForWebsite(this.url) }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,8 +61,11 @@
|
||||
<div class="text-sm text-slate-500">
|
||||
<div v-if="!expanded">
|
||||
{{ truncatedDesc }}
|
||||
<a v-if="description.length >= truncateLength" @click="expandText"
|
||||
>Read More</a
|
||||
<a
|
||||
v-if="description.length >= truncateLength"
|
||||
@click="expandText"
|
||||
class="uppercase text-xs font-semibold text-slate-700"
|
||||
>... Read More</a
|
||||
>
|
||||
</div>
|
||||
<div v-else>
|
||||
@@ -65,7 +73,7 @@
|
||||
<a
|
||||
@click="collapseText"
|
||||
class="uppercase text-xs font-semibold text-slate-700"
|
||||
>Read Less</a
|
||||
>- Read Less</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,8 +175,10 @@
|
||||
{{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }}
|
||||
</span>
|
||||
<span v-if="offer.amount">
|
||||
<fa icon="coins" class="fa-fw text-slate-400"></fa>
|
||||
{{ offer.amount }}
|
||||
<fa
|
||||
:icon="iconForUnitCode(offer.unit)"
|
||||
class="fa-fw text-slate-400"
|
||||
/>{{ offer.amount }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="offer.objectDescription" class="text-slate-500">
|
||||
@@ -195,9 +205,11 @@
|
||||
><fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
|
||||
</span>
|
||||
<span v-if="give.amount"
|
||||
><fa icon="coins" class="fa-fw text-slate-400"></fa>
|
||||
{{ give.amount }}
|
||||
<span v-if="give.amount">
|
||||
<fa
|
||||
:icon="iconForUnitCode(give.unit)"
|
||||
class="fa-fw text-slate-400"
|
||||
/>{{ give.amount }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="give.description" class="text-slate-500">
|
||||
@@ -265,6 +277,7 @@ import { accountsDB, db } from "@/db/index";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { isGlobalUri } from "@/libs/util";
|
||||
import {
|
||||
didInfo,
|
||||
GiverInputInfo,
|
||||
@@ -307,6 +320,7 @@ export default class ProjectViewView extends Vue {
|
||||
timeSince = "";
|
||||
truncatedDesc = "";
|
||||
truncateLength = 40;
|
||||
url = "";
|
||||
|
||||
async created() {
|
||||
await db.open();
|
||||
@@ -408,6 +422,7 @@ export default class ProjectViewView extends Vue {
|
||||
this.truncatedDesc = this.description.slice(0, this.truncateLength);
|
||||
this.latitude = resp.data.claim?.location?.geo?.latitude || 0;
|
||||
this.longitude = resp.data.claim?.location?.geo?.longitude || 0;
|
||||
this.url = resp.data.claim?.url || "";
|
||||
} else if (resp.status === 404) {
|
||||
// actually, axios throws an error so we never get here
|
||||
this.$notify(
|
||||
@@ -625,5 +640,52 @@ export default class ProjectViewView extends Vue {
|
||||
openOfferDialog() {
|
||||
(this.$refs.customOfferDialog as OfferDialog).open();
|
||||
}
|
||||
|
||||
UNIT_CODES: Record<string, Record<string, string>> = {
|
||||
BTC: {
|
||||
name: "Bitcoin",
|
||||
faIcon: "bitcoin-sign",
|
||||
},
|
||||
HUR: {
|
||||
name: "hours",
|
||||
faIcon: "clock",
|
||||
},
|
||||
USD: {
|
||||
name: "US Dollars",
|
||||
faIcon: "dollar",
|
||||
},
|
||||
};
|
||||
|
||||
iconForUnitCode(unitCode: string) {
|
||||
return this.UNIT_CODES[unitCode]?.faIcon || "question";
|
||||
}
|
||||
|
||||
// return an HTTPS URL if it's not a global URL
|
||||
addScheme(url: string) {
|
||||
if (!isGlobalUri(url)) {
|
||||
return "https://" + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// return just the domain for display, if possible
|
||||
domainForWebsite(url: string) {
|
||||
try {
|
||||
const hostname = new URL(url).hostname;
|
||||
if (!hostname) {
|
||||
// happens for non-http URLs
|
||||
return url;
|
||||
} else if (url.endsWith(hostname)) {
|
||||
// it's just the domain
|
||||
return hostname;
|
||||
} else {
|
||||
// there's more, but don't bother displaying the whole thing
|
||||
return hostname + "...";
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// must not be a valid URL
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user