Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b9ae96006 | ||
|
|
4dd5664462 | ||
|
|
7d6a45061d | ||
|
|
3b32c2b156 | ||
|
|
1ee6203f4c | ||
|
|
d93299c352 | ||
|
|
9aea7a576d | ||
|
|
714bb169fa | ||
|
|
606d9ec734 | ||
|
|
7a3bd069b8 | ||
|
|
b1ac9e71cb | ||
|
|
c1176fa24d | ||
|
|
1cf6660e6c | ||
|
|
6957678474 | ||
|
|
889b6d5737 | ||
|
|
1be10b1511 | ||
|
|
85405317ee | ||
|
|
072497a553 | ||
|
|
8a33ccfdcf | ||
|
|
7311d36726 | ||
|
|
7e819ea4de | ||
|
|
5670f23bf3 |
@@ -1,4 +1,4 @@
|
||||
# kickstart-for-time-pwa
|
||||
# TimeSafari.app - Crowd-Funder for Time - PWA
|
||||
|
||||
## Project setup
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kickstart-for-time-pwa",
|
||||
"version": "0.1.3",
|
||||
"name": "crowd-funder-for-time-pwa",
|
||||
"version": "0.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kickstart-for-time-pwa",
|
||||
"version": "0.1.3",
|
||||
"name": "crowd-funder-for-time-pwa",
|
||||
"version": "0.1.4",
|
||||
"dependencies": {
|
||||
"@ethersproject/hdnode": "^5.7.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kickstart-for-time-pwa",
|
||||
"version": "0.1.3",
|
||||
"name": "crowd-funder-for-time-pwa",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
@@ -72,13 +72,13 @@
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"postcss": "^8.4.29",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,16 +1,14 @@
|
||||
|
||||
tasks:
|
||||
|
||||
- don't show "Give" & "Offer" on project screen if they don't have an identifier
|
||||
- allow some gives even if they aren't registered
|
||||
|
||||
- in endorser-push-server - mount folder for persistent sqlite DB outside of container
|
||||
- 40 notifications :
|
||||
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
||||
|
||||
- .5 add project ID to the URL of the project-view, to make a project publicly-accessible
|
||||
- .5 add link to further project / people when a project pays ahead
|
||||
- .5 show checkbox on "gave" modal to say that user is recipient (so it could be uncheked for someone unspecified)
|
||||
|
||||
- .5 allow to manage their notifications even without an identity
|
||||
- .5 bug - on the discover page, enter a search term and search and see a duplicate project show at the end of the list
|
||||
- 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s)
|
||||
- .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
|
||||
|
||||
@@ -21,6 +19,7 @@ tasks:
|
||||
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
|
||||
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164 assignee:trent
|
||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||
- fix cert generation (since it didn't happen automatically for Nov 30)
|
||||
|
||||
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
||||
|
||||
@@ -32,8 +31,11 @@ tasks:
|
||||
- .5 Display a more appealing confirmation on the map when erasing the marker
|
||||
- .5 make a VC details page, or link to endorser.ch
|
||||
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
|
||||
- .5 include the hash of the latest commit on help page next to version
|
||||
- .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 download of each VC (to show that they can actually own their data)
|
||||
|
||||
- contacts v+ :
|
||||
- 01 Import all the non-sensitive data (ie. contacts & settings).
|
||||
@@ -49,6 +51,7 @@ tasks:
|
||||
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
||||
|
||||
- Release Minimum Viable Product :
|
||||
- .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).
|
||||
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
> kickstart-for-time-pwa@0.1.0 build
|
||||
> vue-cli-service build
|
||||
|
||||
All browser targets in the browserslist configuration have supported ES module.
|
||||
Therefore we don't build two separate bundles for differential loading.
|
||||
|
||||
|
||||
WARNING Compiled with 5 warnings6:06:43 PM
|
||||
|
||||
[eslint]
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/components/World/components/objects/landmarks.js
|
||||
98:11 warning Unexpected console statement no-console
|
||||
133:7 warning Unexpected console statement no-console
|
||||
144:5 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/router/index.ts
|
||||
210:3 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/AccountViewView.vue
|
||||
362:7 warning Unexpected console statement no-console
|
||||
375:7 warning Unexpected console statement no-console
|
||||
404:7 warning Unexpected console statement no-console
|
||||
516:7 warning Unexpected console statement no-console
|
||||
536:7 warning Unexpected console statement no-console
|
||||
630:5 warning Unexpected console statement no-console
|
||||
682:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactAmountsView.vue
|
||||
206:9 warning Unexpected console statement no-console
|
||||
233:9 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactGiftingView.vue
|
||||
244:9 warning Unexpected console statement no-console
|
||||
267:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ContactsView.vue
|
||||
340:9 warning Unexpected console statement no-console
|
||||
577:9 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/DiscoverView.vue
|
||||
315:9 warning Unexpected console statement no-console
|
||||
343:7 warning Unexpected console statement no-console
|
||||
390:9 warning Unexpected console statement no-console
|
||||
423:7 warning Unexpected console statement no-console
|
||||
532:9 warning Unexpected console statement no-console
|
||||
575:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/HomeView.vue
|
||||
349:9 warning Unexpected console statement no-console
|
||||
498:9 warning Unexpected console statement no-console
|
||||
521:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/IdentitySwitcherView.vue
|
||||
142:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ImportAccountView.vue
|
||||
123:9 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ImportDerivedAccountView.vue
|
||||
159:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/NewEditProjectView.vue
|
||||
183:9 warning Unexpected console statement no-console
|
||||
215:7 warning Unexpected console statement no-console
|
||||
297:13 warning Unexpected console statement no-console
|
||||
320:11 warning Unexpected console statement no-console
|
||||
345:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ProjectViewView.vue
|
||||
387:9 warning Unexpected console statement no-console
|
||||
421:7 warning Unexpected console statement no-console
|
||||
457:7 warning Unexpected console statement no-console
|
||||
552:9 warning Unexpected console statement no-console
|
||||
554:11 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/ProjectsView.vue
|
||||
131:9 warning Unexpected console statement no-console
|
||||
144:7 warning Unexpected console statement no-console
|
||||
221:9 warning Unexpected console statement no-console
|
||||
237:7 warning Unexpected console statement no-console
|
||||
|
||||
/home/matthew/projects/kick-starter-for-time-pwa/src/views/SeedBackupView.vue
|
||||
94:7 warning Unexpected console statement no-console
|
||||
|
||||
✖ 44 problems (0 errors, 44 warnings)
|
||||
|
||||
|
||||
You may use special comments to disable some warnings.
|
||||
Use // eslint-disable-next-line to ignore the next line.
|
||||
Use /* eslint-disable */ to ignore all warnings in a file.
|
||||
warning
|
||||
|
||||
/models/lupine_plant/textures/lambert2SG_baseColor.png is 3.75 MB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.
|
||||
|
||||
warning
|
||||
|
||||
/models/lupine_plant/textures/lambert2SG_normal.png is 4.91 MB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.
|
||||
|
||||
warning
|
||||
|
||||
asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
|
||||
This can impact web performance.
|
||||
Assets:
|
||||
js/project.44f30c9f.js (318 KiB)
|
||||
js/statistics.8a97010a.js (586 KiB)
|
||||
js/chunk-vendors.a4845bfb.js (411 KiB)
|
||||
js/705.f6a6ce2a.js (252 KiB)
|
||||
img/textures/leafy-autumn-forest-floor.jpg (705 KiB)
|
||||
models/lupine_plant/textures/lambert2SG_baseColor.png (3.58 MiB)
|
||||
models/lupine_plant/textures/lambert2SG_normal.png (4.69 MiB)
|
||||
|
||||
warning
|
||||
|
||||
entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
|
||||
Entrypoints:
|
||||
app (447 KiB)
|
||||
js/chunk-vendors.a4845bfb.js
|
||||
css/app.8f21529c.css
|
||||
js/app.8833cebc.js
|
||||
|
||||
|
||||
File Size Gzipped
|
||||
|
||||
dist/js/statistics.8a97010a.js 585.72 KiB 148.80 KiB
|
||||
dist/js/chunk-vendors.a4845bfb.js 411.44 KiB 137.82 KiB
|
||||
dist/js/project.44f30c9f.js 317.61 KiB 78.67 KiB
|
||||
dist/js/705.f6a6ce2a.js 251.66 KiB 87.12 KiB
|
||||
dist/js/891.33615e4f.js 147.32 KiB 42.09 KiB
|
||||
dist/js/153.e2c8e249.js 146.26 KiB 42.21 KiB
|
||||
dist/js/820.13565d16.js 66.10 KiB 18.33 KiB
|
||||
dist/js/contact-qr.e170ec33.js 54.85 KiB 15.63 KiB
|
||||
dist/js/772.7b4c53a7.js 30.29 KiB 7.21 KiB
|
||||
dist/js/361.898a4525.js 27.40 KiB 8.19 KiB
|
||||
dist/js/account.77d86130.js 17.51 KiB 5.93 KiB
|
||||
dist/js/app.8833cebc.js 17.31 KiB 5.84 KiB
|
||||
dist/js/contacts.3fc90ff8.js 16.94 KiB 5.52 KiB
|
||||
dist/js/discover.24106939.js 15.30 KiB 5.22 KiB
|
||||
dist/js/536.3bb13201.js 15.23 KiB 4.84 KiB
|
||||
dist/workbox-5b385ed2.js 14.11 KiB 4.93 KiB
|
||||
dist/js/home.218b99dd.js 13.89 KiB 4.97 KiB
|
||||
dist/js/help.50d3117b.js 12.49 KiB 4.38 KiB
|
||||
dist/js/projects.417a6cb7.js 8.71 KiB 3.00 KiB
|
||||
dist/js/contact-amounts.a32b0ccd.js 8.44 KiB 3.25 KiB
|
||||
dist/js/229.120e09bf.js 7.99 KiB 2.72 KiB
|
||||
dist/js/identity-switcher.c7937333.js 7.44 KiB 2.52 KiB
|
||||
dist/js/new-edit-project.0552181b.js 7.36 KiB 3.11 KiB
|
||||
dist/js/300.dcaeb2a3.js 6.56 KiB 3.24 KiB
|
||||
dist/js/seed-backup.76a0f7b3.js 3.99 KiB 1.97 KiB
|
||||
dist/js/import-derive.c688d4b8.js 3.81 KiB 1.82 KiB
|
||||
dist/js/import-account.c3fa35fd.js 3.54 KiB 1.66 KiB
|
||||
dist/js/new-edit-account.bb763be2.js 3.39 KiB 1.51 KiB
|
||||
dist/js/431.5a6d64e0.js 3.38 KiB 2.56 KiB
|
||||
dist/service-worker.js 3.37 KiB 1.38 KiB
|
||||
dist/js/scan-contact.46be989a.js 2.79 KiB 1.18 KiB
|
||||
dist/js/start.091a7740.js 2.70 KiB 1.30 KiB
|
||||
dist/js/new-identifier.bb379420.js 2.12 KiB 1.18 KiB
|
||||
dist/js/93.b873dbbf.js 2.08 KiB 1.61 KiB
|
||||
dist/js/new-edit-commitment.9248d367.j 1.96 KiB 1.05 KiB
|
||||
s
|
||||
dist/js/confirm-contact.02004d1d.js 1.89 KiB 1.04 KiB
|
||||
dist/js/858.ae4c08ec.js 0.97 KiB 0.78 KiB
|
||||
dist/css/app.8f21529c.css 18.41 KiB 4.39 KiB
|
||||
dist/css/discover.73ee9bd3.css 14.77 KiB 6.25 KiB
|
||||
dist/css/new-edit-project.73ee9bd3.css 14.77 KiB 6.25 KiB
|
||||
dist/css/contacts.abb5e493.css 0.40 KiB 0.23 KiB
|
||||
dist/css/contact-amounts.5b26ccd4.css 0.31 KiB 0.20 KiB
|
||||
dist/css/home.828bc66e.css 0.25 KiB 0.19 KiB
|
||||
dist/css/project.828bc66e.css 0.25 KiB 0.19 KiB
|
||||
dist/css/statistics.828bc66e.css 0.25 KiB 0.19 KiB
|
||||
|
||||
Images and other types of assets omitted.
|
||||
Build at: 2023-09-07T10:06:43.972Z - Hash: 2b39fcd4d0e78263 - Time: 32016ms
|
||||
|
||||
DONE Build complete. The dist directory is ready to be deployed.
|
||||
INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
|
||||
|
||||
+125
-46
@@ -261,7 +261,29 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-facing-decorator";
|
||||
import axios from "axios";
|
||||
import axios, { AxiosError } from "axios";
|
||||
interface ServiceWorkerMessage {
|
||||
type: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface ServiceWorkerResponse {
|
||||
// Define the properties and their types
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Example interface for error
|
||||
interface ErrorResponse {
|
||||
message: string;
|
||||
// Other properties as needed
|
||||
}
|
||||
|
||||
interface VapidResponse {
|
||||
data: {
|
||||
vapidKey: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
@@ -269,45 +291,87 @@ export default class App extends Vue {
|
||||
mounted() {
|
||||
axios
|
||||
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid")
|
||||
.then((response) => {
|
||||
.then((response: VapidResponse) => {
|
||||
this.b64 = response.data.vapidKey;
|
||||
console.log(this.b64);
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
console.log("New service worker is now controlling the page");
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: AxiosError) => {
|
||||
console.error("API error", error);
|
||||
});
|
||||
}
|
||||
|
||||
private sendMessageToServiceWorker(
|
||||
message: ServiceWorkerMessage,
|
||||
): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
messageChannel.port1.onmessage = (event: MessageEvent) => {
|
||||
if (event.data.error) {
|
||||
reject(event.data.error as ErrorResponse);
|
||||
} else {
|
||||
resolve(event.data as ServiceWorkerResponse);
|
||||
}
|
||||
};
|
||||
|
||||
navigator.serviceWorker.controller.postMessage(message, [
|
||||
messageChannel.port2,
|
||||
]);
|
||||
} else {
|
||||
reject("Service worker controller not available");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private askPermission(): Promise<NotificationPermission> {
|
||||
// Check if Notifications are supported
|
||||
if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
|
||||
return Promise.reject("Service worker not available.");
|
||||
}
|
||||
|
||||
const secret = localStorage.getItem("secret");
|
||||
if (!secret) {
|
||||
return Promise.reject("No secret found.");
|
||||
}
|
||||
|
||||
return this.sendSecretToServiceWorker(secret)
|
||||
.then(() => this.checkNotificationSupport())
|
||||
.then(() => this.requestNotificationPermission())
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
|
||||
private sendSecretToServiceWorker(secret: string): Promise<void> {
|
||||
const message: ServiceWorkerMessage = {
|
||||
type: "SEND_LOCAL_DATA",
|
||||
data: secret,
|
||||
};
|
||||
|
||||
return this.sendMessageToServiceWorker(message).then((response) => {
|
||||
console.log("Response from service worker:", response);
|
||||
});
|
||||
}
|
||||
|
||||
private checkNotificationSupport(): Promise<void> {
|
||||
if (!("Notification" in window)) {
|
||||
alert("This browser does not support notifications.");
|
||||
return Promise.reject("This browser does not support notifications.");
|
||||
}
|
||||
|
||||
// Check existing permissions
|
||||
if (Notification.permission === "granted") {
|
||||
return Promise.resolve("granted");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Request permission
|
||||
return new Promise((resolve, reject) => {
|
||||
const permissionResult = Notification.requestPermission((result) => {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
if (permissionResult) {
|
||||
permissionResult.then(resolve, reject);
|
||||
}
|
||||
}).then((permissionResult) => {
|
||||
console.log("Permission result:", permissionResult);
|
||||
|
||||
if (permissionResult !== "granted") {
|
||||
private requestNotificationPermission(): Promise<NotificationPermission> {
|
||||
return Notification.requestPermission().then((permission) => {
|
||||
if (permission !== "granted") {
|
||||
alert("We need notification permission to provide certain features.");
|
||||
return Promise.reject("We weren't granted permission.");
|
||||
throw new Error("We weren't granted permission.");
|
||||
}
|
||||
|
||||
return permissionResult;
|
||||
return permission;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -366,34 +430,49 @@ export default class App extends Vue {
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
// The subscribeToPush method
|
||||
private subscribeToPush(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if ("serviceWorker" in navigator && "PushManager" in window) {
|
||||
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
|
||||
const options: PushSubscriptionOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey,
|
||||
};
|
||||
console.log(options);
|
||||
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
return registration.pushManager.subscribe(options);
|
||||
})
|
||||
.then((subscription) => {
|
||||
console.log("Push subscription successful:", subscription);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Push subscription failed:", error, options);
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
if (!("serviceWorker" in navigator && "PushManager" in window)) {
|
||||
const errorMsg = "Push messaging is not supported";
|
||||
console.warn(errorMsg);
|
||||
reject(new Error(errorMsg));
|
||||
return reject(new Error(errorMsg));
|
||||
}
|
||||
|
||||
if (Notification.permission !== "granted") {
|
||||
const errorMsg = "Notification permission not granted";
|
||||
console.warn(errorMsg);
|
||||
return reject(new Error(errorMsg));
|
||||
}
|
||||
|
||||
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
|
||||
const options: PushSubscriptionOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey,
|
||||
};
|
||||
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
return registration.pushManager.subscribe(options);
|
||||
})
|
||||
.then((subscription) => {
|
||||
console.log("Push subscription successful:", subscription);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"Subscription or server communication failed:",
|
||||
error,
|
||||
options,
|
||||
);
|
||||
|
||||
// Inform the user about the issue
|
||||
alert(
|
||||
"We encountered an issue setting up push notifications. " +
|
||||
"If you wish to revoke notification permissions, please do so in your browser settings.",
|
||||
);
|
||||
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
placeholder="What was received"
|
||||
v-model="description"
|
||||
/>
|
||||
<div class="flex flex-row mb-6">
|
||||
<div class="flex flex-row">
|
||||
<span
|
||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center px-2 py-2"
|
||||
>Hours</span
|
||||
@@ -33,7 +33,13 @@
|
||||
<fa icon="chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center mb-2 italic">Sign & Send to publish to the world</p>
|
||||
<div v-if="showGivenToUser" class="mt-2 text-right">
|
||||
<input type="checkbox" class="mr-2" v-model="givenToUser" />
|
||||
<label class="text-sm">Given to you</label>
|
||||
</div>
|
||||
<p class="text-center mb-2 mt-6 italic">
|
||||
Sign & Send to publish to the world
|
||||
</p>
|
||||
<button
|
||||
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||
@click="confirm"
|
||||
@@ -70,12 +76,14 @@ export default class GiftedDialog extends Vue {
|
||||
|
||||
@Prop message = "";
|
||||
@Prop projectId = "";
|
||||
@Prop showGivenToUser = false;
|
||||
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
|
||||
giver?: GiverInputInfo;
|
||||
giver?: GiverInputInfo; // undefined means no identified giver agent
|
||||
description = "";
|
||||
givenToUser = false;
|
||||
hours = "0";
|
||||
visible = false;
|
||||
|
||||
@@ -103,11 +111,17 @@ export default class GiftedDialog extends Vue {
|
||||
}
|
||||
|
||||
open(giver: GiverInputInfo) {
|
||||
this.description = "";
|
||||
this.giver = giver;
|
||||
// if we show "given to user" selection, default checkbox to true
|
||||
this.givenToUser = this.showGivenToUser;
|
||||
this.hours = "0";
|
||||
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
// close the dialog but don't change values (since it might be submitting info)
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
@@ -121,8 +135,13 @@ export default class GiftedDialog extends Vue {
|
||||
|
||||
cancel() {
|
||||
this.close();
|
||||
this.eraseValues();
|
||||
}
|
||||
|
||||
eraseValues() {
|
||||
this.description = "";
|
||||
this.giver = undefined;
|
||||
this.givenToUser = this.showGivenToUser;
|
||||
this.hours = "0";
|
||||
}
|
||||
|
||||
@@ -138,14 +157,12 @@ export default class GiftedDialog extends Vue {
|
||||
1000,
|
||||
);
|
||||
// this is asynchronous, but we don't need to wait for it to complete
|
||||
this.recordGive(
|
||||
await this.recordGive(
|
||||
this.giver?.did as string | undefined,
|
||||
this.description,
|
||||
parseFloat(this.hours),
|
||||
).then(() => {
|
||||
this.description = "";
|
||||
this.giver = undefined;
|
||||
this.hours = "0";
|
||||
this.eraseValues();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,7 +226,7 @@ export default class GiftedDialog extends Vue {
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
this.givenToUser ? this.activeDid : undefined,
|
||||
description,
|
||||
hours,
|
||||
this.projectId,
|
||||
@@ -238,7 +255,7 @@ export default class GiftedDialog extends Vue {
|
||||
title: "Success",
|
||||
text: "That gift was recorded.",
|
||||
},
|
||||
10000,
|
||||
7000,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
register("/additional-scripts.js", {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<GiftedDialog ref="customDialog" message="Received from"> </GiftedDialog>
|
||||
<GiftedDialog
|
||||
ref="customDialog"
|
||||
message="Received from"
|
||||
showGivenToUser="true"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -20,16 +20,18 @@
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Play with display options: https://qr-code-styling.com/
|
||||
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
|
||||
-->
|
||||
<QRCodeVue3
|
||||
:value="this.qrValue"
|
||||
:cornersSquareOptions="{ type: 'extra-rounded' }"
|
||||
:dotsOptions="{ type: 'square' }"
|
||||
class="flex justify-center"
|
||||
/>
|
||||
<div @click="onCopyToClipboard()">
|
||||
<!--
|
||||
Play with display options: https://qr-code-styling.com/
|
||||
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
|
||||
-->
|
||||
<QRCodeVue3
|
||||
:value="this.qrValue"
|
||||
:cornersSquareOptions="{ type: 'extra-rounded' }"
|
||||
:dotsOptions="{ type: 'square' }"
|
||||
class="flex justify-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1>
|
||||
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
|
||||
@@ -40,6 +42,7 @@
|
||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { QrcodeStream } from "vue-qrcode-reader";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
|
||||
import { accountsDB, db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
@@ -179,5 +182,22 @@ export default class ContactQRScanShow extends Vue {
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
onCopyToClipboard() {
|
||||
useClipboard()
|
||||
.copy(this.qrValue)
|
||||
.then(() => {
|
||||
console.log("Contact URL:", this.qrValue);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: "Contact URL was copied to clipboard.",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</h1>
|
||||
|
||||
<!-- Quick Search -->
|
||||
<div id="QuickSearch" class="mb-4 flex" v-on:keyup.enter="searchAll()">
|
||||
<div id="QuickSearch" class="mb-4 flex" v-on:keyup.enter="searchSelected()">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchTerms"
|
||||
@@ -17,7 +17,7 @@
|
||||
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
||||
/>
|
||||
<button
|
||||
@click="searchAll()"
|
||||
@click="searchSelected()"
|
||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||
>
|
||||
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
||||
@@ -41,8 +41,10 @@
|
||||
Nearby
|
||||
<span
|
||||
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||
>{{ localCount > -1 ? localCount : "?" }}</span
|
||||
v-if="isLocalActive"
|
||||
>
|
||||
{{ localCount > -1 ? localCount : "?" }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -59,8 +61,10 @@
|
||||
Anywhere
|
||||
<span
|
||||
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||
>{{ remoteCount > -1 ? remoteCount : "?" }}</span
|
||||
v-if="isRemoteActive"
|
||||
>
|
||||
{{ remoteCount > -1 ? remoteCount : "?" }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -156,12 +160,12 @@ export default class DiscoverView extends Vue {
|
||||
apiServer = "";
|
||||
searchTerms = "";
|
||||
projects: ProjectData[] = [];
|
||||
isLoading = false;
|
||||
isLocalActive = true;
|
||||
isRemoteActive = false;
|
||||
localCount = -1;
|
||||
remoteCount = -1;
|
||||
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
||||
isLoading = false;
|
||||
|
||||
// make this function available to the Vue template
|
||||
didInfo = didInfo;
|
||||
@@ -188,6 +192,19 @@ export default class DiscoverView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
public resetCounts() {
|
||||
this.localCount = -1;
|
||||
this.remoteCount = -1;
|
||||
}
|
||||
|
||||
public async searchSelected() {
|
||||
if (this.isLocalActive) {
|
||||
await this.searchLocal();
|
||||
} else {
|
||||
await this.searchAll();
|
||||
}
|
||||
}
|
||||
|
||||
public async buildHeaders(): Promise<HeadersInit> {
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
@@ -213,6 +230,13 @@ export default class DiscoverView extends Vue {
|
||||
}
|
||||
|
||||
public async searchAll(beforeId?: string) {
|
||||
this.resetCounts();
|
||||
|
||||
if (!beforeId) {
|
||||
// this was an initial search so clear any previous results
|
||||
this.projects = [];
|
||||
}
|
||||
|
||||
let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms);
|
||||
|
||||
if (beforeId) {
|
||||
@@ -275,13 +299,21 @@ export default class DiscoverView extends Vue {
|
||||
}
|
||||
|
||||
public async searchLocal(beforeId?: string) {
|
||||
this.resetCounts();
|
||||
|
||||
if (!this.searchBox) {
|
||||
this.projects = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!beforeId) {
|
||||
// this was an initial search so clear any previous results
|
||||
this.projects = [];
|
||||
}
|
||||
|
||||
const claimContents =
|
||||
"claimContents=" + encodeURIComponent(this.searchTerms);
|
||||
|
||||
let queryParams = [
|
||||
claimContents,
|
||||
"minLocLat=" + this.searchBox.bbox.minLat,
|
||||
@@ -376,7 +408,7 @@ export default class DiscoverView extends Vue {
|
||||
onClickLoadProject(id: string) {
|
||||
localStorage.setItem("projectId", id);
|
||||
const route = {
|
||||
name: "project",
|
||||
path: "/project/" + encodeURIComponent(id),
|
||||
};
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
@@ -189,6 +189,15 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Where can I read more?</h2>
|
||||
<p>
|
||||
This is part of the
|
||||
<a href="https://livesofgiving.org" class="text-blue-500">
|
||||
Lives of Giving
|
||||
</a>
|
||||
initiative.
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">What app version is this?</h2>
|
||||
<p>
|
||||
{{ package.version }}
|
||||
|
||||
@@ -81,7 +81,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GiftedDialog ref="customDialog" message="Received from"> </GiftedDialog>
|
||||
<GiftedDialog
|
||||
ref="customDialog"
|
||||
message="Received from"
|
||||
showGivenToUser="true"
|
||||
/>
|
||||
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
v-model="includeLocation"
|
||||
@change="includeLocation = true"
|
||||
@click="includeLocation = !includeLocation"
|
||||
/>
|
||||
<label for="includeLocation">Include Location</label>
|
||||
</div>
|
||||
|
||||
@@ -210,35 +210,37 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||
Contributions By This Project
|
||||
</h3>
|
||||
<button
|
||||
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
||||
class="text-blue-500"
|
||||
<div class="grid items-start grid-cols-1 gap-4">
|
||||
<div
|
||||
v-if="fulfillersToThis.length > 0"
|
||||
class="bg-slate-100 px-4 py-3 rounded-md"
|
||||
>
|
||||
{{ fulfilledByThis.name }}
|
||||
</button>
|
||||
</div>
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||
Contributions To This Project
|
||||
</h3>
|
||||
<ul>
|
||||
<li v-for="plan in fulfillersToThis" :key="plan.handleId">
|
||||
<button
|
||||
@click="onClickLoadProject(plan.handleId)"
|
||||
class="text-blue-500"
|
||||
>
|
||||
{{ plan.name }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="fulfillersToThis.length > 0"
|
||||
class="bg-slate-100 px-4 py-3 rounded-md"
|
||||
>
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||
Contributions To This Project
|
||||
</h3>
|
||||
<ul>
|
||||
<li v-for="plan in fulfillersToThis" :key="plan.handleId">
|
||||
<button
|
||||
@click="onClickLoadProject(plan.handleId)"
|
||||
class="text-blue-500"
|
||||
>
|
||||
{{ plan.name }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||
Contributions By This Project
|
||||
</h3>
|
||||
<button
|
||||
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
||||
class="text-blue-500"
|
||||
>
|
||||
{{ fulfilledByThis.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ export default class ProjectsView extends Vue {
|
||||
onClickLoadProject(id: string) {
|
||||
localStorage.setItem("projectId", id);
|
||||
const route = {
|
||||
name: "project",
|
||||
path: "/project/" + encodeURIComponent(id),
|
||||
};
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,65 @@
|
||||
const notifications = require("./safari-notifications.js");
|
||||
/* eslint-env serviceworker */
|
||||
/* global workbox */
|
||||
importScripts(
|
||||
"https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js",
|
||||
);
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
console.error(event);
|
||||
importScripts(
|
||||
"safari-notifications.js",
|
||||
"nacl.js",
|
||||
"noble-curves.js",
|
||||
"noble-hashes.js",
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("push", function (event) {
|
||||
let payload;
|
||||
if (event.data) {
|
||||
payload = JSON.parse(event.data.text());
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
try {
|
||||
let payload;
|
||||
if (event.data) {
|
||||
payload = JSON.parse(event.data.text());
|
||||
}
|
||||
const message = await self.getNotificationCount();
|
||||
console.error(message);
|
||||
const title = payload ? payload.title : "Custom Title";
|
||||
const options = {
|
||||
body: message,
|
||||
icon: payload ? payload.icon : "icon.png",
|
||||
badge: payload ? payload.badge : "badge.png",
|
||||
};
|
||||
await self.registration.showNotification(title, options);
|
||||
} catch (error) {
|
||||
console.error("Error in processing the push event:", error);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data && event.data.type === "SEND_LOCAL_DATA") {
|
||||
self.secret = event.data.data;
|
||||
event.ports[0].postMessage({ success: true });
|
||||
}
|
||||
|
||||
const title = payload ? payload.title : "Custom Title";
|
||||
const options = {
|
||||
body: payload ? payload.body : "Custom body text",
|
||||
icon: payload ? payload.icon : "icon.png",
|
||||
badge: payload ? payload.badge : "badge.png",
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
});
|
||||
|
||||
|
||||
self.addEventListener("message", function (event) {
|
||||
const data = event.data;
|
||||
|
||||
const result = notifications.getNotificationCount()
|
||||
|
||||
switch (data.command) {
|
||||
case "account":
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unknown command:", data.command);
|
||||
}
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(clients.claim());
|
||||
console.log("Service worker activated", event);
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
console.log(event.request);
|
||||
});
|
||||
|
||||
self.addEventListener("error", (event) => {
|
||||
console.error("Error in Service Worker:", event.message);
|
||||
console.error("File:", event.filename);
|
||||
console.error("Line:", event.lineno);
|
||||
console.error("Column:", event.colno);
|
||||
console.error("Error Object:", event.error);
|
||||
});
|
||||
|
||||
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
+1051
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+524
-5147
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -11,8 +11,9 @@ module.exports = defineConfig({
|
||||
iconPaths: {
|
||||
faviconSVG: "img/icons/safari-pinned-tab.svg",
|
||||
},
|
||||
workboxPluginMode: "InjectManifest",
|
||||
workboxOptions: {
|
||||
importScripts: ["additional-scripts.js"],
|
||||
swSrc: "./sw_scripts/additional-scripts.js",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user