forked from trent_larson/crowd-funder-for-time-pwa
- Add "Set Visibility" dialog for meeting members who need visibility settings - Filter members to show only those not in contacts or without seesMe set - Implement checkbox selection with "Select All" functionality - Add reactive checkbox behavior with proper state management - Default all checkboxes to checked when dialog opens - Implement "Set Visibility" action to add contacts and set seesMe property - Add success notifications with count of affected members - Disable "Set Visibility" button when no members are selected - Use notification callbacks for data refresh - Hide "Set Visibility" buttons when no members need visibility settings - Add proper dialog state management and cleanup - Ensure dialog closes before triggering data refresh to prevent stale states The implementation provides a smooth user experience for managing member visibility settings with proper state synchronization between components.
881 lines
29 KiB
Vue
881 lines
29 KiB
Vue
<template>
|
|
<router-view />
|
|
|
|
<!-- Messages in the upper-right - https://github.com/emmanuelsw/notiwind -->
|
|
<NotificationGroup group="alert">
|
|
<div
|
|
class="fixed z-[120] top-[max(1rem,env(safe-area-inset-top),var(--safe-area-inset-top,0px))] right-4 left-4 sm:left-auto sm:w-full sm:max-w-sm flex flex-col items-start justify-end"
|
|
>
|
|
<Notification
|
|
v-slot="{ notifications, close }"
|
|
enter="transform ease-out duration-300 transition"
|
|
enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4"
|
|
enter-to="translate-y-0 opacity-100 sm:translate-x-0"
|
|
leave="transition ease-in duration-500"
|
|
leave-from="opacity-100"
|
|
leave-to="opacity-0"
|
|
move="transition duration-500"
|
|
move-delay="delay-300"
|
|
>
|
|
<div
|
|
v-for="notification in notifications"
|
|
:key="notification.id"
|
|
class="w-full"
|
|
role="alert"
|
|
>
|
|
<div
|
|
v-if="notification.type === 'toast'"
|
|
class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
|
|
>
|
|
<div class="w-full px-4 py-3 overflow-hidden">
|
|
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
|
{{ notification.title }}
|
|
</h4>
|
|
<p class="text-sm text-ellipsis overflow-hidden">
|
|
{{ notification.text }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'info'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
|
>
|
|
<font-awesome
|
|
icon="circle-info"
|
|
class="fa-fw fa-xl"
|
|
></font-awesome>
|
|
</div>
|
|
|
|
<div
|
|
class="relative w-full pl-4 pr-8 py-2 text-slate-900 overflow-hidden"
|
|
>
|
|
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
|
{{ notification.title }}
|
|
</h4>
|
|
<p class="text-sm text-ellipsis overflow-hidden">
|
|
{{ notification.text }}
|
|
</p>
|
|
|
|
<button
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
|
@click="close(notification.id)"
|
|
>
|
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'success'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-emerald-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
|
>
|
|
<font-awesome
|
|
icon="circle-info"
|
|
class="fa-fw fa-xl"
|
|
></font-awesome>
|
|
</div>
|
|
|
|
<div
|
|
class="relative w-full pl-4 pr-8 py-2 text-emerald-900 overflow-hidden"
|
|
>
|
|
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
|
{{ notification.title }}
|
|
</h4>
|
|
<p class="text-sm text-ellipsis overflow-hidden">
|
|
{{ notification.text }}
|
|
</p>
|
|
|
|
<button
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
|
@click="close(notification.id)"
|
|
>
|
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'warning'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-amber-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
|
>
|
|
<font-awesome
|
|
icon="triangle-exclamation"
|
|
class="fa-fw fa-xl"
|
|
></font-awesome>
|
|
</div>
|
|
|
|
<div
|
|
class="relative w-full pl-4 pr-8 py-2 text-amber-900 overflow-hidden"
|
|
>
|
|
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
|
{{ notification.title }}
|
|
</h4>
|
|
<p class="text-sm text-ellipsis overflow-hidden">
|
|
{{ notification.text }}
|
|
</p>
|
|
|
|
<button
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
|
@click="close(notification.id)"
|
|
>
|
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'danger'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-rose-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
|
>
|
|
<font-awesome
|
|
icon="triangle-exclamation"
|
|
class="fa-fw fa-xl"
|
|
></font-awesome>
|
|
</div>
|
|
|
|
<div
|
|
class="relative w-full pl-4 pr-8 py-2 text-rose-900 overflow-hidden"
|
|
>
|
|
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
|
{{ notification.title }}
|
|
</h4>
|
|
<p class="text-sm text-ellipsis overflow-hidden">
|
|
{{ notification.text }}
|
|
</p>
|
|
|
|
<button
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
|
@click="close(notification.id)"
|
|
>
|
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Notification>
|
|
</div>
|
|
</NotificationGroup>
|
|
|
|
<!--
|
|
This "group" of "modal" is the prompt for an answer.
|
|
Set "type" as follows: "confirm" for yes/no, and "notification" ones:
|
|
"-permission", "-mute", "-off"
|
|
-->
|
|
<NotificationGroup group="modal">
|
|
<div
|
|
class="fixed z-[100] top-[max(env(safe-area-inset-top),var(--safe-area-inset-top,0px))] inset-x-0 w-full"
|
|
>
|
|
<Notification
|
|
v-slot="{ notifications, close }"
|
|
enter="transform ease-out duration-300 transition"
|
|
enter-from="translate-y-2 opacity-0 sm:translate-y-4"
|
|
enter-to="translate-y-0 opacity-100 sm:translate-y-0"
|
|
leave="transition ease-in duration-500"
|
|
leave-from="opacity-100"
|
|
leave-to="opacity-0"
|
|
move="transition duration-500"
|
|
move-delay="delay-300"
|
|
>
|
|
<!-- see NotificationIface in constants/app.ts -->
|
|
<div
|
|
v-for="notification in notifications"
|
|
:key="notification.id"
|
|
class="w-full"
|
|
role="alert"
|
|
>
|
|
<!--
|
|
Type of "confirm" will post a message.
|
|
With onYes function, show a "Yes" button to call that function.
|
|
With onNo function, show a "No" button to call that function,
|
|
and pass it state of "askAgain" field shown if you set promptToStopAsking.
|
|
-->
|
|
<div
|
|
v-if="notification.type === 'confirm'"
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<span class="font-semibold text-lg">
|
|
{{ notification.title }}
|
|
</span>
|
|
<p class="text-sm mb-2">{{ notification.text }}</p>
|
|
|
|
<button
|
|
v-if="notification.onYes"
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
@click="
|
|
notification.onYes();
|
|
close(notification.id);
|
|
"
|
|
>
|
|
Yes{{
|
|
notification.yesText ? ", " + notification.yesText : ""
|
|
}}
|
|
</button>
|
|
|
|
<button
|
|
v-if="notification.onNo"
|
|
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
|
@click="
|
|
notification.onNo(stopAsking);
|
|
close(notification.id);
|
|
stopAsking = false; // reset value
|
|
"
|
|
>
|
|
No{{ notification.noText ? ", " + notification.noText : "" }}
|
|
</button>
|
|
|
|
<label
|
|
v-if="notification.promptToStopAsking && notification.onNo"
|
|
for="toggleStopAsking"
|
|
class="flex items-center justify-between cursor-pointer my-4"
|
|
@click="stopAsking = !stopAsking"
|
|
>
|
|
<!-- label -->
|
|
<span class="ml-2">... and do not ask again.</span>
|
|
<!-- toggle -->
|
|
<div class="relative ml-2">
|
|
<!-- input -->
|
|
<input
|
|
v-model="stopAsking"
|
|
type="checkbox"
|
|
name="stopAsking"
|
|
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>
|
|
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
@click="
|
|
notification.onCancel
|
|
? notification.onCancel(stopAsking)
|
|
: null;
|
|
close(notification.id);
|
|
stopAsking = false; // reset value for next time they open this modal
|
|
"
|
|
>
|
|
{{ notification.onYes ? "Cancel" : "Close" }}
|
|
</button>
|
|
</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"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<p class="text-lg mb-4">Mute app notifications:</p>
|
|
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 1 Day
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 2 Days
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 1 Week
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
Until I turn it back on
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
@click="close(notification.id)"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</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"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<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> this notification?
|
|
</p>
|
|
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
|
@click="
|
|
close(notification.id);
|
|
turnOffNotifications(notification);
|
|
"
|
|
>
|
|
Turn Off Notification
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
@click="close(notification.id)"
|
|
>
|
|
Leave it On
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'set-visibility-meeting-members'"
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-lg mx-auto my-4 overflow-y-scroll bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<h3 class="text-lg font-semibold leading-[1.25] mb-2">
|
|
Set Visibility to Meeting Members
|
|
</h3>
|
|
<p class="text-sm mb-4">
|
|
Would you like to <b>make your activities visible</b> to the
|
|
following members? (This will also add them as contacts if
|
|
they aren't already.)
|
|
</p>
|
|
|
|
<!-- Custom table area - you can customize this -->
|
|
<div
|
|
v-if="shouldInitializeSelection(notification)"
|
|
class="mb-4"
|
|
>
|
|
<table
|
|
class="w-full border-collapse border border-slate-300 text-sm text-start"
|
|
>
|
|
<thead
|
|
v-if="
|
|
notification.membersData &&
|
|
notification.membersData.length > 0
|
|
"
|
|
>
|
|
<tr class="bg-slate-100 font-medium">
|
|
<th class="border border-slate-300 px-3 py-2">
|
|
<label class="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
:checked="isAllSelected(notification)"
|
|
:indeterminate="isIndeterminate(notification)"
|
|
@change="toggleSelectAll(notification)"
|
|
/>
|
|
Select All
|
|
</label>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Dynamic data from MembersList -->
|
|
<tr
|
|
v-if="
|
|
!notification.membersData ||
|
|
notification.membersData.length === 0
|
|
"
|
|
>
|
|
<td
|
|
class="border border-slate-300 px-3 py-2 text-center italic text-gray-500"
|
|
>
|
|
No members need visibility settings
|
|
</td>
|
|
</tr>
|
|
<tr
|
|
v-for="member in notification.membersData || []"
|
|
:key="member.member.memberId"
|
|
>
|
|
<td class="border border-slate-300 px-3 py-2">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<label class="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
:checked="isMemberSelected(member.did)"
|
|
@change="toggleMemberSelection(member.did)"
|
|
/>
|
|
{{ member.name || SOMEONE_UNNAMED }}
|
|
</label>
|
|
|
|
<!-- Friend indicator - only show if they are already a contact -->
|
|
<font-awesome
|
|
v-if="member.isContact"
|
|
icon="user-circle"
|
|
class="fa-fw ms-auto text-slate-400 cursor-pointer hover:text-slate-600"
|
|
@click="showContactInfo"
|
|
/>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<button
|
|
v-if="
|
|
notification.membersData &&
|
|
notification.membersData.length > 0
|
|
"
|
|
:disabled="!hasSelectedMembers"
|
|
:class="[
|
|
'block w-full text-center text-md font-bold uppercase px-2 py-2 rounded-md',
|
|
hasSelectedMembers
|
|
? 'bg-blue-600 text-white cursor-pointer'
|
|
: 'bg-slate-400 text-slate-200 cursor-not-allowed',
|
|
]"
|
|
@click="
|
|
setVisibilityForSelectedMembers(notification);
|
|
closeDialog(notification, close);
|
|
"
|
|
>
|
|
Set Visibility
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
@click="closeDialog(notification, close)"
|
|
>
|
|
{{
|
|
notification.membersData &&
|
|
notification.membersData.length > 0
|
|
? "Maybe Later"
|
|
: "Cancel"
|
|
}}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Notification>
|
|
</div>
|
|
</NotificationGroup>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Vue, Component } from "vue-facing-decorator";
|
|
|
|
import { NotificationIface } from "./constants/app";
|
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
|
import { logger } from "./utils/logger";
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
import { SOMEONE_UNNAMED } from "@/constants/entities";
|
|
import { setVisibilityUtil } from "./libs/endorserServer";
|
|
|
|
interface Settings {
|
|
notifyingNewActivityTime?: string;
|
|
notifyingReminderTime?: string;
|
|
}
|
|
|
|
@Component({
|
|
components: {},
|
|
mixins: [PlatformServiceMixin],
|
|
})
|
|
export default class App extends Vue {
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
stopAsking = false;
|
|
selectedMembers: string[] = [];
|
|
selectionInitialized = false;
|
|
|
|
get hasSelectedMembers() {
|
|
return this.selectedMembers.length > 0;
|
|
}
|
|
activeDid = "";
|
|
apiServer = "";
|
|
|
|
async created() {
|
|
// Initialize settings
|
|
const settings = await this.$accountSettings();
|
|
this.apiServer = settings.apiServer || "";
|
|
|
|
// Get activeDid from active_identity table
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const activeIdentity = await (this as any).$getActiveIdentity();
|
|
this.activeDid = activeIdentity.activeDid || "";
|
|
}
|
|
|
|
isAllSelected(notification: NotificationIface) {
|
|
const membersData = notification?.membersData || [];
|
|
if (!membersData || membersData.length === 0) return false;
|
|
return membersData.every((member) =>
|
|
this.selectedMembers.includes(member.did),
|
|
);
|
|
}
|
|
|
|
isIndeterminate(notification: NotificationIface) {
|
|
const membersData = notification?.membersData || [];
|
|
if (!membersData || membersData.length === 0) return false;
|
|
const selectedCount = membersData.filter((member) =>
|
|
this.selectedMembers.includes(member.did),
|
|
).length;
|
|
return selectedCount > 0 && selectedCount < membersData.length;
|
|
}
|
|
|
|
toggleSelectAll(notification: NotificationIface) {
|
|
const membersData = notification?.membersData || [];
|
|
if (!membersData || membersData.length === 0) return;
|
|
|
|
if (this.isAllSelected(notification)) {
|
|
// Deselect all
|
|
this.selectedMembers = [];
|
|
} else {
|
|
// Select all
|
|
this.selectedMembers = membersData.map((member) => member.did);
|
|
}
|
|
}
|
|
|
|
toggleMemberSelection(memberDid: string) {
|
|
const index = this.selectedMembers.indexOf(memberDid);
|
|
if (index > -1) {
|
|
this.selectedMembers.splice(index, 1);
|
|
} else {
|
|
this.selectedMembers.push(memberDid);
|
|
}
|
|
}
|
|
|
|
isMemberSelected(memberDid: string) {
|
|
return this.selectedMembers.includes(memberDid);
|
|
}
|
|
|
|
shouldInitializeSelection(notification: NotificationIface) {
|
|
// This method will initialize selection when the dialog opens
|
|
if (
|
|
notification?.type === "set-visibility-meeting-members" &&
|
|
!this.selectionInitialized
|
|
) {
|
|
this.initializeSelection(notification);
|
|
this.selectionInitialized = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
initializeSelection(notification: NotificationIface) {
|
|
// Reset selection when dialog opens
|
|
this.selectedMembers = [];
|
|
// Select all by default
|
|
const membersData = notification?.membersData || [];
|
|
this.selectedMembers = membersData.map((member) => member.did);
|
|
}
|
|
|
|
resetSelection() {
|
|
this.selectedMembers = [];
|
|
this.selectionInitialized = false;
|
|
}
|
|
|
|
async setVisibilityForSelectedMembers(notification: NotificationIface) {
|
|
try {
|
|
const membersData = notification?.membersData || [];
|
|
const selectedMembers = membersData.filter((member) =>
|
|
this.selectedMembers.includes(member.did),
|
|
);
|
|
|
|
let successCount = 0;
|
|
|
|
for (const member of selectedMembers) {
|
|
try {
|
|
// If they're not a contact yet, add them as a contact first
|
|
if (!member.isContact) {
|
|
await this.addAsContact(member);
|
|
}
|
|
|
|
// Set their seesMe to true
|
|
await this.updateContactVisibility(member.did, true);
|
|
|
|
successCount++;
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`Error processing member ${member.did}:`, error);
|
|
// Continue with other members even if one fails
|
|
}
|
|
}
|
|
|
|
// Show success notification
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "success",
|
|
title: "Visibility Set Successfully",
|
|
text: `Visibility set for ${successCount} member${successCount === 1 ? "" : "s"}.`,
|
|
},
|
|
5000,
|
|
);
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("Error setting visibility:", error);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: "Failed to set visibility for some members. Please try again.",
|
|
},
|
|
5000,
|
|
);
|
|
}
|
|
}
|
|
|
|
async addAsContact(member: { did: string; name: string }) {
|
|
try {
|
|
const newContact = {
|
|
did: member.did,
|
|
name: member.name,
|
|
};
|
|
|
|
await this.$insertContact(newContact);
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("Error adding contact:", err);
|
|
if (err instanceof Error && err.message?.indexOf("already exists") > -1) {
|
|
// Contact already exists, continue
|
|
} else {
|
|
throw err; // Re-throw if it's not a duplicate error
|
|
}
|
|
}
|
|
}
|
|
|
|
async updateContactVisibility(did: string, seesMe: boolean) {
|
|
try {
|
|
// Get the contact object
|
|
const contact = await this.$getContact(did);
|
|
if (!contact) {
|
|
throw new Error(`Contact not found for DID: ${did}`);
|
|
}
|
|
|
|
// Use the proper API to set visibility on the server
|
|
const result = await setVisibilityUtil(
|
|
this.activeDid,
|
|
this.apiServer,
|
|
this.axios,
|
|
contact,
|
|
seesMe,
|
|
);
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Failed to set visibility");
|
|
}
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("Error updating contact visibility:", err);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async closeDialog(
|
|
notification: NotificationIface,
|
|
closeFn: (id: string) => void,
|
|
) {
|
|
this.resetSelection();
|
|
|
|
// Close the notification first
|
|
closeFn(notification.id);
|
|
|
|
// Then call the callback after a short delay to ensure dialog is closed
|
|
setTimeout(async () => {
|
|
if (notification.callback) {
|
|
await notification.callback();
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
showContactInfo() {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "info",
|
|
title: "Contact Info",
|
|
text: "This user is already your contact, but your activities are not visible to them yet.",
|
|
},
|
|
5000,
|
|
);
|
|
}
|
|
|
|
async turnOffNotifications(
|
|
notification: NotificationIface,
|
|
): Promise<boolean> {
|
|
let subscription: PushSubscriptionJSON | null = null;
|
|
let allGoingOff = false;
|
|
|
|
try {
|
|
const settings: Settings = await this.$accountSettings();
|
|
|
|
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
|
const notifyingReminder = !!settings?.notifyingReminderTime;
|
|
|
|
if (!notifyingNewActivity || !notifyingReminder) {
|
|
allGoingOff = true;
|
|
}
|
|
|
|
await navigator.serviceWorker?.ready
|
|
.then((registration) => {
|
|
return registration.pushManager.getSubscription();
|
|
})
|
|
.then(async (subscript: PushSubscription | null) => {
|
|
if (subscript) {
|
|
subscription = subscript.toJSON();
|
|
|
|
if (allGoingOff) {
|
|
await subscript.unsubscribe();
|
|
}
|
|
} else {
|
|
this.$logAndConsole("Subscription object is not available.");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
this.$logAndConsole(
|
|
"Push provider server communication failed: " +
|
|
JSON.stringify(error),
|
|
true,
|
|
);
|
|
logger.error("Error during subscription fetch:", error);
|
|
});
|
|
|
|
if (!subscription) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "info",
|
|
title: "Finished",
|
|
text: "Notifications are off.",
|
|
},
|
|
5000,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
const serverSubscription = {
|
|
...(subscription as PushSubscriptionJSON),
|
|
...(allGoingOff ? {} : { notifyType: notification.title }),
|
|
};
|
|
|
|
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(serverSubscription),
|
|
})
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
this.$logAndConsole(
|
|
`Push server failed: ${response.status} ${errorBody}`,
|
|
true,
|
|
);
|
|
logger.error("Push server error response:", errorBody);
|
|
}
|
|
return response.ok;
|
|
})
|
|
.catch((error) => {
|
|
this.$logAndConsole(
|
|
"Push server communication failed: " + JSON.stringify(error),
|
|
true,
|
|
);
|
|
logger.error("Error during server communication:", error);
|
|
return false;
|
|
});
|
|
|
|
const message = pushServerSuccess
|
|
? "Notification is off."
|
|
: "Notification is still on. Try to turn it off again.";
|
|
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "info",
|
|
title: "Finished",
|
|
text: message,
|
|
},
|
|
5000,
|
|
);
|
|
|
|
if (notification.callback) {
|
|
notification.callback(pushServerSuccess);
|
|
}
|
|
|
|
return pushServerSuccess;
|
|
} catch (error) {
|
|
this.$logAndConsole(
|
|
"Error turning off notifications: " + JSON.stringify(error),
|
|
true,
|
|
);
|
|
logger.error("Critical error in turnOffNotifications:", error);
|
|
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "error",
|
|
title: "Error",
|
|
text: "Failed to turn off notifications. Please try again.",
|
|
},
|
|
5000,
|
|
);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
#Content {
|
|
padding-left: max(
|
|
1.5rem,
|
|
env(safe-area-inset-left),
|
|
var(--safe-area-inset-left, 0px)
|
|
);
|
|
padding-right: max(
|
|
1.5rem,
|
|
env(safe-area-inset-right),
|
|
var(--safe-area-inset-right, 0px)
|
|
);
|
|
padding-top: max(
|
|
1.5rem,
|
|
env(safe-area-inset-top),
|
|
var(--safe-area-inset-top, 0px)
|
|
);
|
|
padding-bottom: max(
|
|
1.5rem,
|
|
env(safe-area-inset-bottom),
|
|
var(--safe-area-inset-bottom, 0px)
|
|
);
|
|
}
|
|
|
|
#QuickNav ~ #Content {
|
|
padding-bottom: calc(
|
|
max(env(safe-area-inset-bottom), var(--safe-area-inset-bottom, 0px)) +
|
|
6.333rem
|
|
);
|
|
}
|
|
</style>
|