Compare commits

..

25 Commits

Author SHA1 Message Date
Jose Olarte III
b4b78f6a2c Recolored buttons 2023-12-18 19:46:58 +08:00
Jose Olarte III
3c0f6ce0de Design and uniformity tweaks 2023-12-18 19:44:03 +08:00
5534f8fa50 fix logic for prod & test host detection 2023-12-17 20:17:45 -07:00
a5004d475e bump version to next -beta 2023-12-17 20:02:28 -07:00
b445b1234f bump version to 0.1.6 2023-12-17 20:00:12 -07:00
17c96dd01a fix linting, etc with previous feature (env warning) 2023-12-16 17:17:54 -07:00
6ad17101b2 Merge pull request 'add warning if on unexpected server' (#98) from server-warn into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#98
2023-12-16 10:08:17 -05:00
b4085ffaa7 Merge branch 'master' into server-warn 2023-12-16 08:08:00 -07:00
4f2cb55753 add warning if on unexpected server 2023-12-16 08:04:16 -07:00
ebf9164ecc Merge pull request 'add infinite scroll to the home page feed' (#97) from home-infinite into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#97
2023-12-15 07:49:01 -05:00
540cc21839 add infinite scroll to the home page feed 2023-12-14 21:54:15 -07:00
c182068901 give more details on map-overlap issue 2023-12-13 21:27:13 -07:00
aaa1f31945 fix one single linting problem 2023-12-13 21:16:17 -07:00
17c632eb16 on brand new ID, go back home (plus some task adjustments) 2023-12-13 20:19:29 -07:00
41c4cbe61a fix more messaging and actions if they don't have an ID 2023-12-13 20:05:58 -07:00
c8402797ad remove "mute" notifications (since they can turn them off, and the mute functionality isn't built) 2023-12-13 19:53:46 -07:00
4a09b9b9b1 more fixes for when there is no identity 2023-12-13 19:46:40 -07:00
5db3423301 enhance error messages 2023-12-13 19:17:18 -07:00
2b00b243e8 fix error when lacking ID, and format linked projects 2023-12-13 19:13:04 -07:00
f2e5d8168d Merge pull request 'design-tweaks-2023-12' (#96) from design-tweaks-2023-12 into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#96
2023-12-13 11:20:54 -05:00
1d262b8da9 Merge branch 'master' into design-tweaks-2023-12 2023-12-13 11:17:33 -05:00
8ed74b71f2 change colors and add spacing to make buttons obvious 2023-12-13 09:16:45 -07:00
8fb21c3d89 doc: update tasks, update verbiage, and add to help doc 2023-12-13 08:58:04 -07:00
8dbfcd38d3 fix bad text in pop-up, and fix name of class 2023-12-13 08:57:24 -07:00
04df0d4eff fix problem when there's a null description 2023-12-13 08:56:53 -07:00
22 changed files with 494 additions and 230 deletions

View File

@@ -9,7 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.6]
## [0.1.6] - 2023.12.17
### Added
- Infinite scroll on home page
### Changed
- UI improvements
- Show web-push subscription info
- Icon
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad

View File

@@ -26,13 +26,15 @@ If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js,
...to make sure the service worker scripts are in proper form
* Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit. Tag wth the new version: `git tag 0.1.0`
* Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit.
* Tag wth the new version: `git tag 0.1.0`. Increment version, add "-beta", `npm install`, and commit.
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be PROD.
* `npm run build`
* Revert src/constants/app.ts & change version to "-beta"
* Revert src/constants/app.ts
* `cp sw_scripts/[ns]* dist/`

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "TimeSafari",
"version": "0.1.6-beta",
"version": "0.1.7-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "TimeSafari",
"version": "0.1.6-beta",
"version": "0.1.7-beta",
"dependencies": {
"@ethersproject/hdnode": "^5.7.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",

View File

@@ -1,6 +1,6 @@
{
"name": "TimeSafari",
"version": "0.1.6-beta",
"version": "0.1.7-beta",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",

View File

@@ -2,28 +2,26 @@
tasks:
- 08 notifications :
- get it to work on Android
- get it to work on Android - background data in apps
- get it to work on iOS
- lock down regenerate_vapid endpoint (so only we admins can do it on demand)
- make the app behave correctly when App Notifications are turned off
- remove "mute notifications"
- remove sleep in py-push-server app.py?
- see if we can detect OS-level notifications if turned off
- write troubleshooting docs for notifications
- make the "App Notifications" toggle on when they turn notifications on
- make the "App Notifications" toggle off when they turn notifications off
- in py-push-server, when sending a push to a subscriber and we get on a 410 "error #106", delete the subscription record
- https://gitea.anomalistdesign.com/trent_larson/py-push-server/pulls/3/files
- remove "notification push server" advanced setting since it only makes sense on the current domain
- hide the "App Notifications" toggle when they switch notifications
- prompt user to install on their home screen https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
- warn if they're using the web (android only?)
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
https://web.dev/articles/get-installed-related-apps
- add windows & mac help at OS & browser level
- .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
- back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
- fix the projects on /discover to show the issuer (currently all "Someone Anonymous")
- .5 Add infinite scroll to gifts on the home page
- .3 bug - make or edit a project, choose "Include location", and see the map display shows on top of the bottom icons assignee-group:ui
- Got error adding on Firefox user #0 as contact for themselves
- .5 If notifications are not enabled, add message to front page with link/button to enable
- fix notification error when first loading the app
- add note after contact addition that they can see your info
- 01 server - show all claim details when issued by the issuer
- enhance help page instructions for debugging
- add way to test quickly a push notification
- help instructions for PWA install problems (secret failed, must reinstall)
@@ -48,8 +46,10 @@ tasks:
- Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- 01 send visibility signal as a VC and store it
- remove 'rowid' references (that are sqlite-specific)
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
- allow some gives even if they aren't registered
- 01 make the prod build copy the sw_scripts
- .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?
@@ -124,6 +124,8 @@ tasks:
- 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better.
- 16 From the home screen, make the quick action even easier.
- allow some gives even if they aren't registered - maybe someday as a gift to the world, but we really want this to be built via personal connections
log:
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27

View File

@@ -328,6 +328,12 @@ export default class App extends Vue {
);
}
} catch (error) {
if (window.location.host.startsWith("localhost")) {
console.log(
"Ignoring this error getting VAPID for local development:",
error,
);
} else {
console.error("Got an error initializing notifications:", error);
this.$notify(
{
@@ -340,6 +346,7 @@ export default class App extends Vue {
);
}
}
}
private sendMessageToServiceWorker(
message: ServiceWorkerMessage,

View File

@@ -0,0 +1,58 @@
<template>
<div class="text-center text-red-500">{{ message }}</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { AppString } from "@/constants/app";
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component
export default class TopMessage extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@Prop selected = "";
message = "";
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (
settings?.warnIfTestServer &&
window.location.hostname !== AppString.PROD_TIME_SAFARI_SERVER_HOST
) {
const didPrefix = settings.activeDid?.slice(11, 14);
this.message = "You're linked to a test server, user " + didPrefix;
} else if (
settings?.warnIfProdServer &&
window.location.hostname === AppString.PROD_TIME_SAFARI_SERVER_HOST
) {
const didPrefix = settings.activeDid?.slice(1, 14);
this.message =
"You're linked to the production server, user " + didPrefix;
}
} catch (err: unknown) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Detecting Server",
text: JSON.stringify(err),
},
10000,
);
}
}
}
</script>

View File

@@ -6,12 +6,16 @@
export enum AppString {
APP_NAME = "Time Safari",
PROD_TIME_SAFARI_SERVER_HOST = "timesafari.app",
PROD_TIME_SAFARI_SERVER = "https://" + PROD_TIME_SAFARI_SERVER_HOST,
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.timesafari.app",
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",

View File

@@ -45,8 +45,5 @@ db.on("populate", () => {
db.settings.add({
id: MASTER_SETTINGS_KEY,
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
// remember that things you add from now on aren't automatically in the DB for old users
webPushServer: AppString.DEFAULT_PUSH_SERVER,
});
});

View File

@@ -12,15 +12,17 @@ export type BoundingBox = {
* Settings type encompasses user-specific configuration details.
*/
export type Settings = {
id: number; // Only one entry using MASTER_SETTINGS_KEY
id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY
activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL
firstName?: string; // User's first name
lastName?: string; // deprecated - put all names in firstName
lastViewedClaimId?: string; // Last viewed claim ID
lastNotifiedClaimId?: string; // Last notified claim ID
isRegistered?: boolean;
webPushServer?: string; // Web Push server URL
lastName?: string; // deprecated - put all names in firstName
lastNotifiedClaimId?: string; // Last notified claim ID
lastViewedClaimId?: string; // Last viewed claim ID
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders
// Array of named search boxes defined by bounding boxes
searchBoxes?: Array<{
@@ -30,8 +32,9 @@ export type Settings = {
showContactGivesInline?: boolean; // Display contact inline or not
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders
warnIfProdServer?: boolean; // Warn if using a production server
warnIfTestServer?: boolean; // Warn if using a testing server
webPushServer?: string; // Web Push server URL
};
/**

View File

@@ -1,5 +1,7 @@
<template>
<QuickNav selected="Profile"></QuickNav>
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Heading -->
@@ -32,8 +34,24 @@
</span>
</div>
<!-- ID notice -->
<div
v-if="!activeDid"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
>
<p class="mb-4">
<b>Note:</b> Before you can take any action, you need an ID.
</p>
<router-link
:to="{ name: 'start' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
>
Generate Identity
</router-link>
</div>
<!-- Registration notice -->
<!-- We won't show any loading indicator; we'll just pop the message in once we know they need it. -->
<!-- We won't show any loading indicator because it usually doesn't change anything. We'll just pop the message in only if we discover that they need it. -->
<div
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
@@ -88,6 +106,7 @@
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<label
v-if="notificationUnchanged"
for="toggleNotifications"
class="flex items-center justify-between cursor-pointer"
@click="
@@ -127,37 +146,9 @@
></div>
</div>
</label>
<label
for="toggleMuteNotifications"
class="flex items-center justify-between cursor-pointer mt-4"
@click="
this.$notify(
{
group: 'modal',
type: 'notification-mute',
},
-1,
)
"
>
<!-- label -->
<div>Mute Notifications</div>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
name="toggleMuteNotifications"
class="sr-only"
disabled
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
<label v-else>
Notification status may have changed. Revisit this page to see the
latest setting.
</label>
</div>
@@ -165,6 +156,7 @@
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Backup Identifier Seed
@@ -230,7 +222,7 @@
<div v-if="showAdvanced">
<p class="text-rose-600 mb-8">
Beware: the features here can be confusing and even change data in ways
you do not expect. But we support your freedoms!
you do not expect. But we support your freedom!
</p>
<!-- Deep Identity Details -->
@@ -328,7 +320,7 @@
:to="{ name: 'identity-switcher' }"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Switch Identity / No Identity
Switch Identity
</router-link>
<button
@@ -372,6 +364,46 @@
</button>
</div>
<label
for="toggleProdWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleProdWarning"
>
<!-- label -->
<h2>Show warning if on prod server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<label
for="toggleTestWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleTestWarning"
>
<!-- label -->
<h2>Show warning if on test server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2">
Notification Push Server
@@ -423,6 +455,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@@ -447,7 +480,7 @@ interface IAccount {
derivationPath: string;
}
@Component({ components: { QuickNav } })
@Component({ components: { QuickNav, TopMessage } })
export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@@ -459,6 +492,7 @@ export default class AccountViewView extends Vue {
derivationPath = "";
givenName = "";
isRegistered = false;
notificationUnchanged = true;
numAccounts = 0;
publicHex = "";
publicBase64 = "";
@@ -477,6 +511,8 @@ export default class AccountViewView extends Vue {
showAdvanced = false;
subscription: PushSubscription | null = null;
warnIfProdServer = false;
warnIfTestServer = false;
private isSubscribed = false;
get toggleNotifications() {
@@ -484,6 +520,63 @@ export default class AccountViewView extends Vue {
}
set toggleNotifications(value) {
this.isSubscribed = value;
this.notificationUnchanged = false;
}
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!this.subscription;
} catch (error) {
console.error("Mount error:", error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.showContactGives = !!settings?.showContactGivesInline;
this.warnIfProdServer = !!settings?.warnIfProdServer;
this.warnIfTestServer = !!settings?.warnIfTestServer;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
}
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
@@ -551,6 +644,16 @@ export default class AccountViewView extends Vue {
this.updateShowContactAmounts();
}
toggleProdWarning() {
this.warnIfProdServer = !this.warnIfProdServer;
this.updateWarnIfProdServer(this.warnIfProdServer);
}
toggleTestWarning() {
this.warnIfTestServer = !this.warnIfTestServer;
this.updateWarnIfTestServer(this.warnIfTestServer);
}
readableTime(timeStr: string) {
return timeStr.substring(0, timeStr.indexOf("T"));
}
@@ -560,60 +663,6 @@ export default class AccountViewView extends Vue {
this.numAccounts = await accountsDB.accounts.count();
}
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!this.subscription;
} catch (error) {
console.error("Mount error:", error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
this.showContactGives = !!settings?.showContactGivesInline;
}
/**
* Processes the identity and updates the component's state.
* @param {IdentityType} identity - Object containing identity information.
@@ -687,6 +736,52 @@ export default class AccountViewView extends Vue {
}
}
public async updateWarnIfProdServer(newSetting: boolean) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfProdServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Prod Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
public async updateWarnIfTestServer(newSetting: boolean) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Test Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
/**
* Asynchronously exports the database into a downloadable JSON file.
*

View File

@@ -19,9 +19,9 @@
<!-- Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<div>
<div class="block pb-4 flex gap-4 overflow-hidden">
<div class="block flex gap-4 overflow-hidden">
<div class="overflow-hidden">
<h2 class="text-xl">{{ veriClaim.id }}</h2>
<h2 class="text-md font-bold">{{ veriClaim.id }}</h2>
<div class="text-sm">
<div>
{{ veriClaim.claimType }}
@@ -45,7 +45,7 @@
</div>
<div>
<h2 class="font-bold text-2xl">Confirmations</h2>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
<span v-else-if="totalConfirmers() === 1">
@@ -136,22 +136,24 @@
</div>
<div>
<h2 class="font-bold text-2xl mt-8">Claim</h2>
<pre>{{ util.inspect(veriClaim, false, null) }}</pre>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Claim</h2>
<pre class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md">
{{ util.inspect(veriClaim, false, null) }}
</pre>
</div>
<h2 class="font-bold text-2xl mt-8">Full Claim</h2>
<p>
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Full Claim</h2>
<p class="mb-4">
The full claim includes the claim as it was originally issued, including
the signature (ie. the proof of issuance by that person).
</p>
<div v-if="!fullClaim">
<div v-if="fullClaimMessage">
<p v-if="fullClaimMessage" class="mb-4">
{{ fullClaimMessage }}
</div>
</p>
<button
v-else
class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4"
class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
@click="showFullClaim(veriClaim.id)"
>
Load Full Claim Details
@@ -161,10 +163,12 @@
<pre>{{ util.inspect(fullClaim, false, null) }}</pre>
</div>
<a :href="apiServer + '/api/claim/' + veriClaim.id" target="_blank">
<button class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4">
<a
:href="apiServer + '/api/claim/' + veriClaim.id"
target="_blank"
class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
>
View on the Public Server
</button>
</a>
</section>
</template>

View File

@@ -124,7 +124,7 @@ interface Notification {
}
@Component({ components: { QuickNav } })
export default class ContactsView extends Vue {
export default class ContactAmountssView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
activeDid = "";

View File

@@ -171,7 +171,7 @@
<button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)"
title="givenByMeDescriptions[contact.did]"
:title="givenByMeDescriptions[contact.did] || ''"
>
To:
{{
@@ -190,7 +190,7 @@
<button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
@click="onClickAddGive(contact.did, activeDid)"
title="givenToMeDescriptions[contact.did]"
:title="givenToMeDescriptions[contact.did] || ''"
>
From:
{{
@@ -367,6 +367,10 @@ export default class ContactsView extends Vue {
}
async loadGives() {
if (!this.activeDid) {
return;
}
const handleResponse = (
resp: { status: number; data: { data: GiveServerRecord[] } },
descriptions: Record<string, string>,
@@ -401,11 +405,11 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Retrieval Error",
text:
"Got an error retrieving your " +
(useRecipient ? "given" : "received") +
" time from the server.",
" data from the server.",
},
-1,
);
@@ -456,12 +460,13 @@ export default class ContactsView extends Vue {
this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) {
console.log("Error loading gives", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
text: error as string,
title: "Load Error",
text: "Got an error loading your gives.",
},
-1,
);
@@ -705,6 +710,7 @@ export default class ContactsView extends Vue {
);
}
} catch (error) {
console.error("Error when registering:", error);
let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError;
if (serverError) {
@@ -721,7 +727,7 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Registration Error",
text: userMessage,
},
-1,
@@ -767,27 +773,28 @@ export default class ContactsView extends Vue {
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Bad server response of " + resp.status;
resp.data.error?.message || "Got some error setting visibility.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Setting Visibility",
text: message,
},
-1,
);
}
} catch (err) {
console.error("Got some server error when setting visibility:", err);
console.error("Got some error when setting visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Setting Visibility",
text: "Check connectivity and try again.",
},
-1,
@@ -830,19 +837,19 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Checking Visibility",
text: message,
},
-1,
);
}
} catch (err) {
console.log("Caught error from server request to check visibility:", err);
console.log("Caught error from request to check visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Checking Visibility",
text: "Check connectivity and try again.",
},
-1,
@@ -1028,6 +1035,7 @@ export default class ContactsView extends Vue {
}
}
} catch (error) {
console.log("Error in createAndSubmitGive: ", error);
let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError;
if (serverError) {
@@ -1044,7 +1052,7 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Sending Give",
text: userMessage,
},
-1,

View File

@@ -1,5 +1,6 @@
<template>
<QuickNav selected="Discover"></QuickNav>
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
@@ -136,6 +137,7 @@ import { didInfo, ProjectData } from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
interface Notification {
group: string;
@@ -149,6 +151,7 @@ interface Notification {
QuickNav,
InfiniteScroll,
EntityIcon,
TopMessage,
},
})
export default class DiscoverView extends Vue {

View File

@@ -28,13 +28,35 @@
<p>Somehow call the service-worker self.showNotification</p>
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
<p>
<div>
Walk-throughs & screenshots, maybe for all combinations of OS &
browsers.
</p>
<div>
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
<div>
Notifications require iOS 16.4 or higher. To check your iOS version,
go to Settings > General > About > Software Version.
</div>
<h3 class="text-lg font-semibold">Mobile Phone - Google Android</h3>
<div>See the browser section.</div>
<h3 class="text-lg font-semibold">Desktop - Mac</h3>
<div>Requires Mac OS 13.</div>
</div>
</div>
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
<p>Walk-throughs & screenshots for browser settings</p>
<div>
<div>
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
<div>Make sure your OS (above) supports it.</div>
<h3 class="text-lg font-semibold">Mobile Phone - Android</h3>
<div>Chrome requires version 50.</div>
</div>
</div>
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
<p>

View File

@@ -1,5 +1,7 @@
<template>
<QuickNav selected="Home"></QuickNav>
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@@ -13,11 +15,11 @@
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
<p class="text-lg mb-3">
You need an <b>identifier</b> before you can record others' giving.
You need an <b>identifier</b> before you can record anyone's gives.
</p>
<router-link
:to="{ name: 'start' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
Create Your Identifier</router-link
>
@@ -27,17 +29,17 @@
v-else-if="!isRegistered"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
Someone must register your account before you can record others' giving.
Someone must register your account before you can record anyone's gives.
To do this:
<router-link
:to="{ name: 'contact-qr' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
1. Show Them Your Identity Info</router-link
>
<router-link
:to="{ name: 'account' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
2. Check Your Limits</router-link
>
@@ -103,13 +105,10 @@
showGivenToUser="true"
/>
<!-- Results List -->
<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>
<div :class="{ hidden: isHiddenSpinner }">
<p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
<InfiniteScroll @reached-bottom="loadMoreGives">
<ul class="border-t border-slate-300">
<li
class="border-b border-slate-300 py-2"
@@ -118,10 +117,11 @@
>
<div
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedId"
v-if="record.jwtId == feedLastViewedClaimId"
>
You've seen all the following
</div>
<div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
<span class="">{{ this.giveDescription(record) }}</span>
@@ -131,14 +131,27 @@
</div>
</li>
</ul>
</InfiniteScroll>
<div :class="{ hidden: isHiddenSpinner }">
<p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { db, accountsDB } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
@@ -146,11 +159,7 @@ import {
GiverInputInfo,
GiveServerRecord,
} from "@/libs/endorserServer";
import { Contact } from "@/db/tables/contacts";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { IIdentifier } from "@veramo/core";
import { Account } from "@/db/tables/accounts";
interface Notification {
group: string;
@@ -160,7 +169,13 @@ interface Notification {
}
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
components: {
GiftedDialog,
QuickNav,
EntityIcon,
InfiniteScroll,
TopMessage,
},
})
export default class HomeView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@@ -169,10 +184,9 @@ export default class HomeView extends Vue {
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = "";
feedAllLoaded = false;
feedData = [];
feedPreviousOldestId?: string;
feedLastViewedId?: string;
feedLastViewedClaimId?: string;
isHiddenSpinner = true;
isRegistered = false;
numAccounts = 0;
@@ -212,9 +226,12 @@ export default class HomeView extends Vue {
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedId = settings?.lastViewedClaimId;
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.isRegistered = !!settings?.isRegistered;
// this returns a Promise but we don't need to wait for it
this.updateAllFeed();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
this.$notify(
@@ -257,24 +274,33 @@ export default class HomeView extends Vue {
return headers;
}
/**
* Data loader used by infinite scroller
* @param payload is the flag from the InfiniteScroll indicating if it should load
**/
public async loadMoreGives(payload: boolean) {
if (payload) {
this.updateAllFeed();
}
}
public async updateAllFeed() {
this.isHiddenSpinner = false;
await this.retrieveClaims(this.apiServer, this.feedPreviousOldestId)
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => {
if (results.data.length > 0) {
this.feedData = this.feedData.concat(results.data);
this.feedAllLoaded = results.hitLimit;
this.feedPreviousOldestId =
results.data[results.data.length - 1].jwtId;
// The following update is only done on the first load.
if (
this.feedLastViewedId == null ||
this.feedLastViewedId < results.data[0].jwtId
this.feedLastViewedClaimId == null ||
this.feedLastViewedClaimId < results.data[0].jwtId
) {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
lastViewedClaimId: results.data[0].jwtId,
});
// but not for this page because we need to remember what it was before
}
}
})
@@ -284,17 +310,22 @@ export default class HomeView extends Vue {
{
group: "alert",
type: "danger",
title: "Export Error",
title: "Feed Error",
text: e.userMessage || "There was an error retrieving feed data.",
},
-1,
);
});
this.isHiddenSpinner = true;
}
public async retrieveClaims(endorserApiServer: string, beforeId?: string) {
/**
* Retrieve claims in reverse chronological order
*
* @param beforeId the earliest ID (of previous searches) to search earlier
* @return claims in reverse chronological order
*/
public async retrieveGives(endorserApiServer: string, beforeId?: string) {
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
const response = await fetch(
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,

View File

@@ -37,7 +37,7 @@
maxlength="5000"
></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ fullClaim.description.length }}/5000 max. characters
{{ fullClaim.description?.length }}/5000 max. characters
</div>
<input

View File

@@ -88,7 +88,7 @@ export default class NewIdentifierView extends Vue {
this.loading = false;
setTimeout(() => {
this.$router.push({ name: "account" });
this.$router.push({ name: "home" });
}, 1000);
}
}

View File

@@ -1,5 +1,7 @@
<template>
<QuickNav />
<TopMessage />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
@@ -88,8 +90,8 @@
</button>
</div>
<div class="mb-4">
<div v-if="activeDid" class="text-center">
<div v-if="activeDid" class="mb-4">
<div class="text-center">
<button
@click="openOfferDialog({ name: 'you', did: activeDid })"
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
@@ -99,17 +101,16 @@
</div>
</div>
<div>
<div v-if="activeDid" class="text-center">
<div v-if="activeDid">
<div class="text-center">
<button
@click="openGiftDialog({ name: 'you', did: activeDid })"
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
>
I gave&hellip;
</button>
<p class="mt-2 mb-4 text-center">Or, record a gift from:</p>
<p class="mt-2 mb-4 text-center">Or, record a contribution from:</p>
</div>
<p v-if="!activeDid" class="mt-2 mb-4">Record a gift from:</p>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<li @click="openGiftDialog()">
@@ -234,22 +235,25 @@
<h3 class="text-sm uppercase font-semibold mb-3">
Contributions To This Idea
</h3>
<ul>
<li v-for="plan in fulfillersToThis" :key="plan.handleId">
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<div class="text-center">
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
<button
@click="onClickLoadProject(plan.handleId)"
class="text-blue-500"
>
{{ plan.name }}
</button>
</li>
</ul>
</div>
</div>
</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 Idea
</h3>
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<div class="text-center">
<button
@click="onClickLoadProject(fulfilledByThis.handleId)"
class="text-blue-500"
@@ -259,6 +263,7 @@
</div>
</div>
</div>
</div>
<GiftedDialog
ref="customGiveDialog"
@@ -279,6 +284,7 @@ import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue";
import TopMessage from "@/components/TopMessage.vue";
import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@@ -303,7 +309,7 @@ interface Notification {
}
@Component({
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav, TopMessage },
})
export default class ProjectViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@@ -356,12 +362,6 @@ export default class ProjectViewView extends Vue {
.equals(activeDid)
.first()) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load project records with no identity available.",
);
}
return identity;
}

View File

@@ -1,5 +1,7 @@
<template>
<QuickNav selected="Projects"></QuickNav>
<TopMessage />
<section id="Content" class="p-6 pb-24">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@@ -79,6 +81,7 @@ import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
import { ProjectData } from "@/libs/endorserServer";
interface Notification {
@@ -89,7 +92,7 @@ interface Notification {
}
@Component({
components: { InfiniteScroll, QuickNav, EntityIcon },
components: { InfiniteScroll, QuickNav, EntityIcon, TopMessage },
})
export default class ProjectsView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;

View File

@@ -400,3 +400,22 @@ While notifications are turned on, the user can tap on the App Notifications tog
* Active. (User can change to Muted when the user mutes notifications.)
* Muted. (User can change to Active when the user toggles it.)
(Turning mute off automatically after some amount of time is not planned in version 1.)
# TROUBLESHOOTING
## Desktop
#### Firefox
Go to `about:debugging` and click on `Inspect` for the service worker.
#### Chrome
Go to `chrome://inspect/#service-workers` and click on `Inspect` for the service worker.
## Mobile
#### Android
#### iOS