finish separation of daily reminder message, bump version to 0.3.34

This commit is contained in:
2024-11-24 13:09:40 -07:00
parent 15e09c2d81
commit 2758af6e6e
15 changed files with 448 additions and 128 deletions

View File

@@ -238,6 +238,7 @@
</div>
</div>
</div>
<div
v-if="notification.type === 'notification-mute'"
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
@@ -277,6 +278,7 @@
</div>
</div>
</div>
<div
v-if="notification.type === 'notification-off'"
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
@@ -286,7 +288,7 @@
>
<div class="w-full px-6 py-6 text-slate-900 text-center">
<p class="text-lg mb-4">
Would you like to <b>turn off</b> notifications for this app?
Would you like to <b>turn off</b> this notification?
</p>
<button
@@ -296,7 +298,7 @@
"
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
>
Turn Off Notifications
Turn Off Notification
</button>
<button
@click="close(notification.id)"
@@ -318,9 +320,8 @@
<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import { db, logConsoleAndDb } from "@/db/index";
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
import { NotificationIface } from "./constants/app";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component
export default class App extends Vue {
@@ -329,18 +330,29 @@ export default class App extends Vue {
stopAsking = false;
async turnOffNotifications(notification: NotificationIface) {
let subscription;
const pushProviderSuccess: boolean = await navigator.serviceWorker?.ready
let subscription: object | null = null;
let allGoingOff = false;
const settings = await retrieveSettingsForActiveAccount();
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
const notifyingReminder = !!settings?.notifyingReminderTime;
if (!notifyingNewActivity || !notifyingReminder) {
// the other notification is already off, so fully unsubscribe now
allGoingOff = true;
}
await navigator.serviceWorker?.ready
.then((registration) => {
return registration.pushManager.getSubscription();
})
.then((subscript: PushSubscription | null) => {
subscription = subscript;
.then(async (subscript: PushSubscription | null) => {
if (subscript) {
return subscript.unsubscribe();
subscription = subscript.toJSON();
if (allGoingOff) {
await subscript.unsubscribe();
}
} else {
logConsoleAndDb("Subscription object is not available.");
return false;
}
})
.catch((error) => {
@@ -348,15 +360,20 @@ export default class App extends Vue {
"Push provider server communication failed: " + JSON.stringify(error),
true,
);
return false;
});
const pushServerSuccess: boolean = await fetch("/web-push/unsubscribe", {
const serverSubscription = {
...subscription,
};
if (!allGoingOff) {
serverSubscription["notifyType"] = notification.title;
}
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subscription),
body: JSON.stringify(serverSubscription),
})
.then((response) => {
return response.ok;
@@ -370,34 +387,24 @@ export default class App extends Vue {
});
let message;
if (pushProviderSuccess === pushServerSuccess) {
message = "Both local and server notifications ";
if (pushProviderSuccess) {
message += "are off.";
} else {
message += "failed to turn off.";
}
if (pushServerSuccess) {
message = "Notification is off.";
} else {
message =
"Local unsubscribe " +
(pushProviderSuccess ? "succeeded" : "failed") +
" but server unsubscribe " +
(pushServerSuccess ? "succeeded" : "failed") +
".";
message = "Notification is still on. Try to turn it off again.";
}
this.$notify({
group: "alert",
type: "info",
title: "Finished",
text: message,
});
this.$notify(
{
group: "alert",
type: "info",
title: "Finished",
text: message,
},
5000,
);
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivity: false,
});
if (notification.callback) {
notification.callback(pushProviderSuccess && pushServerSuccess);
// it's OK if the local notifications are still on (especially if the other notification is on)
notification.callback(pushServerSuccess);
}
}
}

View File

@@ -16,7 +16,12 @@
>
<div class="w-full px-6 py-6 text-slate-900 text-center">
<p v-if="serviceWorkerReady && vapidKey" class="text-lg mb-4">
Would you like to be notified of new activity once a day?
<span v-if="pushType === DAILY_CHECK_TITLE">
Would you like to be notified of new activity, up to once a day?
</span>
<span v-else>
Would you like to get a reminder message once a day?
</span>
</p>
<p v-else class="text-lg mb-4">
Waiting for system initialization, which may take up to 5 seconds...
@@ -24,28 +29,51 @@
</p>
<div v-if="serviceWorkerReady && vapidKey">
<span class="flex flex-row justify-center">
<span class="mt-2">Yes, tell me at: </span>
<input
type="number"
@change="checkHourInput"
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
v-model="hourInput"
/>
<input
type="number"
@change="checkMinuteInput"
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
v-model="minuteInput"
/>
<span
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
@click="hourAm = !hourAm"
<div v-if="pushType === DAILY_CHECK_TITLE">
<span>Yes, send me a message when there is new data for me</span>
</div>
<div v-else>
<span>Yes, send me this message:</span>
<!-- eslint-disable -->
<textarea
type="text"
id="push-message"
v-model="messageInput"
class="rounded border border-slate-400 mt-2 px-2 py-2 w-full"
maxlength="100"
></textarea
>
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
<span v-else> PM <fa icon="chevron-up" /> </span>
<!-- eslint-enable -->
<span class="w-full flex justify-between text-xs text-slate-500">
<span></span>
<span>(100 characters max)</span>
</span>
</span>
</div>
<div>
<span class="flex flex-row justify-center">
<span class="mt-2">... at: </span>
<input
type="number"
@change="checkHourInput"
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
v-model="hourInput"
/>
<input
type="number"
@change="checkMinuteInput"
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
v-model="minuteInput"
/>
<span
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
@click="hourAm = !hourAm"
>
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
<span v-else> PM <fa icon="chevron-up" /> </span>
</span>
</span>
</div>
<button
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
@click="
@@ -73,12 +101,7 @@
import { Component, Vue } from "vue-facing-decorator";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import {
db,
logConsoleAndDb,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
import * as libsUtil from "@/libs/util";
@@ -89,6 +112,7 @@ interface ErrorResponse {
// PushSubscriptionJSON is defined in the Push API https://www.w3.org/TR/push-api/#dom-pushsubscriptionjson
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
message?: string;
notifyTime: { utcHour: number; minute: number };
notifyType: string;
}
@@ -115,17 +139,27 @@ export default class PushNotificationPermission extends Vue {
// eslint-disable-next-line
$notify!: (notification: NotificationIface, timeout?: number) => Promise<() => void>;
callback: (success: boolean, time: string) => void = () => {};
DAILY_CHECK_TITLE = libsUtil.DAILY_CHECK_TITLE;
DIRECT_PUSH_TITLE = libsUtil.DIRECT_PUSH_TITLE;
callback: (success: boolean, time: string, message?: string) => void =
() => {};
hourAm = true;
hourInput = "8";
isVisible = false;
messageInput = "";
minuteInput = "00";
pushType = "";
serviceWorkerReady = false;
vapidKey = "";
async open(callback?: (success: boolean, time: string) => void) {
this.isVisible = true;
async open(
pushType: string,
callback?: (success: boolean, time: string, message?: string) => void,
) {
this.callback = callback || this.callback;
this.isVisible = true;
this.pushType = pushType;
try {
const settings = await retrieveSettingsForActiveAccount();
let pushUrl = DEFAULT_PUSH_SERVER;
@@ -194,6 +228,18 @@ export default class PushNotificationPermission extends Vue {
navigator.serviceWorker?.ready.then(() => {
this.serviceWorkerReady = true;
});
if (this.pushType === this.DIRECT_PUSH_TITLE) {
this.messageInput =
"Just a friendly reminder: click and share some gratitude with the world.";
// focus on the message input
setTimeout(function () {
document.getElementById("push-message")?.focus();
}, 100);
} else {
// not critical but doesn't make sense in a daily check
this.messageInput = "";
}
}
private close() {
@@ -264,7 +310,7 @@ export default class PushNotificationPermission extends Vue {
{
group: "alert",
type: "danger",
title: "Browser Notifications Not Supported",
title: "Browser Notifications Are Not Supported",
text: "This browser does not support notifications.",
},
3000,
@@ -329,7 +375,7 @@ export default class PushNotificationPermission extends Vue {
}
}
public async turnOnNotifications() {
private async turnOnNotifications() {
let notifyCloser = () => {};
return this.askPermission()
.then((permission) => {
@@ -378,7 +424,8 @@ export default class PushNotificationPermission extends Vue {
const subscriptionWithTime: PushSubscriptionWithTime = {
notifyTime: { utcHour: finalUtcHour, minute: finalUtcMinute },
notifyType: "DAILY_CHECK",
notifyType: this.pushType,
message: this.messageInput,
...subscription.toJSON(),
};
await this.sendSubscriptionToServer(subscriptionWithTime);
@@ -398,23 +445,21 @@ export default class PushNotificationPermission extends Vue {
);
await libsUtil.sendTestThroughPushServer(subscription, true);
notifyCloser();
this.$notify(
{
group: "alert",
type: "success",
title: "Notifications Turned On",
text: "Notifications are on. You should see at least one on your device; if not, check the 'Troubleshoot' page.",
},
-1,
);
setTimeout(() => {
this.$notify(
{
group: "alert",
type: "success",
title: "Notification Is On",
text: "You should see at least one on your device; if not, check the 'Troubleshoot' link.",
},
7000,
);
}, 500);
const timeText =
// eslint-disable-next-line
this.hourInput + ":" + this.minuteInput + " " + (this.hourAm ? "AM" : "PM");
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivity: true,
notifyingNewActivityTime: timeText,
});
this.callback(true, timeText);
this.callback(true, timeText, this.messageInput);
})
.catch((error) => {
logConsoleAndDb(
@@ -434,14 +479,7 @@ export default class PushNotificationPermission extends Vue {
},
3000,
);
// unsubscribe just in case we failed after getting a subscription
navigator.serviceWorker?.ready
.then((registration) => registration.pushManager.getSubscription())
.then((subscription) => {
if (subscription) {
subscription.unsubscribe();
}
});
// if we want to also unsubscribe, be sure to do that only if no other notification is active
});
}

View File

@@ -16,8 +16,8 @@ export enum AppString {
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
PROD_PARTNER_API_SERVER = "https://endorser.ch",
TEST_PARTNER_API_SERVER = "https://test-partner.endorser.ch",
PROD_PARTNER_API_SERVER = "https://partner-api.endorser.ch",
TEST_PARTNER_API_SERVER = "https://test-partner-api.endorser.ch",
LOCAL_PARTNER_API_SERVER = LOCAL_ENDORSER_API_SERVER,
PROD_PUSH_SERVER = "https://timesafari.app",

View File

@@ -39,8 +39,11 @@ export type Settings = {
lastNotifiedClaimId?: string;
lastViewedClaimId?: string;
notifyingNewActivity?: boolean; // set if they have turned on daily check for new activity via the push server
notifyingNewActivityTime?: string; // set to their chosen time if they have turned on daily check for new activity via the push server
notifyingReminderMessage?: string; // set to their chosen message for a daily reminder
notifyingReminderTime?: string; // set to their chosen time for a daily reminder
partnerApiServer?: string; // partner server API URL
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes

View File

@@ -364,6 +364,11 @@ export const getPasskeyExpirationSeconds = async (): Promise<number> => {
);
};
// These are shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
export const DAILY_CHECK_TITLE = "DAILY_CHECK";
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
export const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
export const sendTestThroughPushServer = async (
subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean,
@@ -374,16 +379,12 @@ export const sendTestThroughPushServer = async (
pushUrl = settings.webPushServer;
}
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const newPayload = {
...subscriptionJSON,
// ... overridden with the following
// eslint-disable-next-line prettier/prettier
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
...subscriptionJSON,
};
console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload);

View File

@@ -184,7 +184,36 @@
<div class="flex items-center justify-between">
<!-- label -->
<div>
New Activity Notifications
Reminder Notification
<fa
icon="question-circle"
class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click.stop="showReminderNotificationInfo"
/>
</div>
<!-- toggle -->
<div
class="relative ml-2 cursor-pointer"
@click="showReminderNotificationChoice()"
>
<!-- input -->
<input type="checkbox" v-model="notifyingReminder" 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>
</div>
<div v-if="notifyingReminder" class="w-full flex justify-between">
<span class="ml-8 mr-8">Message: "{{ notifyingReminderMessage }}"</span>
<span>{{ notifyingReminderTime.replace(" ", "&nbsp;") }}</span>
</div>
<div class="mt-2 flex items-center justify-between">
<!-- label -->
<div>
New Activity Notification
<fa
icon="question-circle"
class="text-slate-400 fa-fw ml-2 cursor-pointer"
@@ -200,7 +229,6 @@
<input
type="checkbox"
v-model="notifyingNewActivity"
name="toggleNotificationsInput"
class="sr-only"
/>
<!-- line -->
@@ -212,7 +240,7 @@
</div>
</div>
<div v-if="notifyingNewActivityTime" class="w-full text-right">
{{ notifyingNewActivityTime }}
{{ notifyingNewActivityTime.replace(" ", "&nbsp;") }}
</div>
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
Troubleshoot your notification setup.
@@ -609,18 +637,51 @@
{{ DEFAULT_PUSH_SERVER }}
</span>
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
<div class="px-3 py-4">
<input
type="text"
class="block w-full rounded border border-slate-400 px-3 py-2"
v-model="partnerApiServerInput"
/>
<button
v-if="partnerApiServerInput != partnerApiServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
@click="onClickSavePartnerServer()"
>
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@click="partnerApiServerInput = AppConstants.PROD_PARTNER_API_SERVER"
>
Use Prod
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@click="partnerApiServerInput = AppConstants.TEST_PARTNER_API_SERVER"
>
Use Test
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@click="partnerApiServerInput = AppConstants.LOCAL_PARTNER_API_SERVER"
>
Use Local
</button>
</div>
<span class="px-4 text-sm" v-if="!partnerApiServerInput">
When that setting is blank, this app will use the default partner server
URL:
{{ DEFAULT_PARTNER_API_SERVER }}
</span>
<div id="sectionImageServerURL" class="mt-2">
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
&nbsp;
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
</div>
<div id="sectionPartnerServerURL" class="mt-2">
<span class="text-slate-500 text-sm font-bold">Partner Server URL</span>
&nbsp;
<span class="text-sm">{{ DEFAULT_PARTNER_API_SERVER }}</span>
</div>
<label
for="toggleHideRegisterPromptOnNewContact"
class="flex items-center justify-between cursor-pointer mt-4"
@@ -778,7 +839,7 @@ import {
ImageRateLimits,
tokenExpiryTimeDescription,
} from "@/libs/endorserServer";
import { getAccount } from "@/libs/util";
import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE, getAccount } from "@/libs/util";
const inputImportFileNameRef = ref<Blob>();
@@ -815,6 +876,11 @@ export default class AccountViewView extends Vue {
loadingLimits = false;
notifyingNewActivity = false;
notifyingNewActivityTime = "";
notifyingReminder = false;
notifyingReminderMessage = "";
notifyingReminderTime = "";
partnerApiServer = "";
partnerApiServerInput = "";
passkeyExpirationDescription = "";
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
@@ -858,7 +924,7 @@ export default class AccountViewView extends Vue {
const registration = await navigator.serviceWorker?.ready;
this.subscription = await registration.pushManager.getSubscription();
if (!this.subscription) {
if (this.notifyingNewActivity) {
if (this.notifyingNewActivity || this.notifyingReminder) {
// the app thought there was a subscription but there isn't, so fix the settings
this.turnOffNotifyingFlags();
}
@@ -909,14 +975,19 @@ export default class AccountViewView extends Vue {
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.imageServer = settings.imageServer || "";
this.profileImageUrl = settings.profileImageUrl;
this.showContactGives = !!settings.showContactGivesInline;
this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact;
this.notifyingNewActivity = !!settings.notifyingNewActivity;
this.isRegistered = !!settings?.isRegistered;
this.imageServer = settings.imageServer || "";
this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
this.notifyingReminder = !!settings.notifyingReminderTime;
this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
this.notifyingReminderTime = settings.notifyingReminderTime || "";
this.partnerApiServer = settings.partnerApiServer || "";
this.partnerApiServerInput = settings.partnerApiServer || "";
this.profileImageUrl = settings.profileImageUrl;
this.showContactGives = !!settings.showContactGivesInline;
this.passkeyExpirationMinutes =
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
@@ -1025,10 +1096,13 @@ export default class AccountViewView extends Vue {
if (!this.notifyingNewActivity) {
(
this.$refs.pushNotificationPermission as PushNotificationPermission
).open((success: boolean, time: string) => {
).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivityTime: timeText,
});
this.notifyingNewActivity = true;
this.notifyingNewActivityTime = time;
this.notifyingNewActivityTime = timeText;
}
});
} else {
@@ -1036,10 +1110,13 @@ export default class AccountViewView extends Vue {
{
group: "modal",
type: "notification-off",
title: "", // unused, only here to satisfy type check
title: DAILY_CHECK_TITLE, // repurposed to indicate the type of notification
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivityTime: "",
});
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
}
@@ -1050,6 +1127,71 @@ export default class AccountViewView extends Vue {
}
}
async showReminderNotificationInfo() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Reminder Notification",
text: `
This will notify you at a specific time each day.
Note that it does not give you personalized notifications,
so if you want less reliable but personalized notification then choose a 'New Activity' Notification.
Do you want more details?
`,
onYes: async () => {
await (this.$router as Router).push({
name: "help-notification-types",
});
},
yesText: "tell me more.",
},
-1,
);
}
async showReminderNotificationChoice() {
if (!this.notifyingReminder) {
(
this.$refs.pushNotificationPermission as PushNotificationPermission
).open(
DIRECT_PUSH_TITLE,
async (success: boolean, timeText: string, message?: string) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingReminderMessage: message,
notifyingReminderTime: timeText,
});
this.notifyingReminder = true;
this.notifyingReminderMessage = message || "";
this.notifyingReminderTime = timeText;
}
},
);
} else {
this.$notify(
{
group: "modal",
type: "notification-off",
title: DIRECT_PUSH_TITLE, // repurposed to indicate the type of notification
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
this.notifyingReminder = false;
this.notifyingReminderMessage = "";
this.notifyingReminderTime = "";
}
},
},
-1,
);
}
}
public async toggleHideRegisterPromptOnNewContact() {
const newSetting = !this.hideRegisterPromptOnNewContact;
await db.open();
@@ -1069,13 +1211,18 @@ export default class AccountViewView extends Vue {
}
public async turnOffNotifyingFlags() {
// should tell the push server as well
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivity: false,
notifyingNewActivityTime: "",
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
this.notifyingReminder = false;
this.notifyingReminderMessage = "";
this.notifyingReminderTime = "";
}
/**
@@ -1424,6 +1571,14 @@ export default class AccountViewView extends Vue {
this.apiServer = this.apiServerInput;
}
async onClickSavePartnerServer() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
partnerApiServer: this.partnerApiServerInput,
});
this.partnerApiServer = this.partnerApiServerInput;
}
async onClickSavePushServer() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {

View File

@@ -69,7 +69,7 @@
<div class="overflow-hidden">
<div class="text-sm">
<div>
<fa icon="arrow-down" class="fa-fw text-slate-400" />
<fa icon="arrow-left" class="fa-fw text-slate-400" />
{{ giverName }}
</div>
<div class="ml-6">gave</div>
@@ -84,7 +84,7 @@
</div>
<div class="ml-6">to</div>
<div>
<fa icon="arrow-up" class="fa-fw text-slate-400" />
<fa icon="arrow-right" class="fa-fw text-slate-400" />
{{ recipientName }}
</div>
<div>

View File

@@ -39,6 +39,15 @@
</p>
</div>
<h2 class="text-xl font-semibold mt-4">Android Users</h2>
<div>
<p>
Note that you may not receive notifications when the app is in the
background. When you're done working, close the app, and then you'll
get the reminder notifications.
</p>
</div>
<h2 class="text-xl font-semibold mt-4">
If this app doesn't support notifications...
<!-- Note that that exact verbiage shows in a message elsewhere. -->

View File

@@ -151,10 +151,12 @@
<input type="checkbox" class="mr-2" v-model="sendToTrustroots" />
<label>Send to Trustroots</label>
</div>
<!--
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
<label>Send to TripHopping</label>
</div>
-->
</div>
<div class="mt-8">
@@ -581,7 +583,12 @@ export default class NewEditProjectView extends Vue {
);
const nostrPubKey = pubPri?.publicKey;
const trustrootsUrl = DEFAULT_PARTNER_API_SERVER + "/api/partner/link";
let partnerServer = DEFAULT_PARTNER_API_SERVER;
const settings = await retrieveSettingsForActiveAccount();
if (settings.partnerApiServer) {
partnerServer = settings.partnerApiServer;
}
const trustrootsUrl = partnerServer + "/api/partner/link";
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
const content = this.fullClaim.name + " - see " + timeSafariUrl;
// Why does IntelliJ not see matching types?