Add Notification Settings

This commit is contained in:
Milad Raeisi
2024-09-29 13:11:21 +04:00
parent ca82e37d03
commit ce7d1de4a2
13 changed files with 94 additions and 879 deletions

View File

@@ -13,16 +13,16 @@ import { provideAngor } from '@angor';
import { TranslocoService, provideTransloco } from '@ngneat/transloco'; import { TranslocoService, provideTransloco } from '@ngneat/transloco';
import { appRoutes } from 'app/app.routes'; import { appRoutes } from 'app/app.routes';
import { provideIcons } from 'app/core/icons/icons.provider'; import { provideIcons } from 'app/core/icons/icons.provider';
import { mockApiServices } from 'app/mock-api';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { TranslocoHttpLoader } from './core/transloco/transloco.http-loader'; import { TranslocoHttpLoader } from './core/transloco/transloco.http-loader';
import { provideServiceWorker } from '@angular/service-worker'; import { provideServiceWorker } from '@angular/service-worker';
import { HashService } from './services/hash.service'; import { HashService } from './services/hash.service';
import { navigationServices } from './layout/navigation/navigation.services';
export function initializeApp(hashService: HashService) { export function initializeApp(hashService: HashService) {
console.log('initializeApp. Getting hashService.load.'); console.log('initializeApp. Getting hashService.load.');
return (): Promise<void> => hashService.load(); return (): Promise<void> => hashService.load();
} }
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideAnimations(), provideAnimations(),
@@ -36,7 +36,7 @@ export const appConfig: ApplicationConfig = {
useFactory: initializeApp, useFactory: initializeApp,
deps: [HashService], deps: [HashService],
multi: true, multi: true,
}, },
provideRouter( provideRouter(
appRoutes, appRoutes,
withPreloading(PreloadAllModules), withPreloading(PreloadAllModules),
@@ -97,7 +97,7 @@ export const appConfig: ApplicationConfig = {
provideAngor({ provideAngor({
mockApi: { mockApi: {
delay: 0, delay: 0,
services: mockApiServices, services: navigationServices,
}, },
angor: JSON.parse(localStorage.getItem('angorConfig')) ?? { angor: JSON.parse(localStorage.getItem('angorConfig')) ?? {
layout: 'classic', layout: 'classic',

View File

@@ -1,156 +1,51 @@
<div class="w-full max-w-3xl"> <div class="w-full max-w-3xl">
<!-- Form --> <!-- Form -->
<form [formGroup]="notificationsForm"> <form [formGroup]="notificationsForm">
<!-- Section --> <div class="w-full text-xl">Notification Settings</div>
<div class="w-full text-xl">Alerts</div>
<div class="mt-8 grid w-full grid-cols-1 gap-6"> <div class="mt-8 grid w-full grid-cols-1 gap-6">
<!-- Communication -->
<div class="flex items-center justify-between">
<div
class="flex-auto cursor-pointer"
(click)="communication.toggle()"
>
<div class="font-medium leading-6">Communication</div>
<div class="text-secondary text-md">
Get news, announcements, and product updates.
</div>
</div>
<mat-slide-toggle
class="ml-2"
[color]="'primary'"
[formControlName]="'communication'"
#communication
>
</mat-slide-toggle>
</div>
<!-- Security -->
<div class="flex items-center justify-between">
<div
class="flex-auto cursor-pointer"
(click)="securityToggle.toggle()"
>
<div class="font-medium leading-6">Security</div>
<div class="text-secondary text-md">
Get important notifications about your account security.
</div>
</div>
<mat-slide-toggle
class="ml-2"
[color]="'primary'"
[formControlName]="'security'"
#securityToggle
>
</mat-slide-toggle>
</div>
<!-- Meetups -->
<div class="flex items-center justify-between">
<div
class="flex-auto cursor-pointer"
(click)="meetupsToggle.toggle()"
>
<div class="font-medium leading-6">Meetups</div>
<div class="text-secondary text-md">
Get an email when a Meetup is posted close to my
location.
</div>
</div>
<mat-slide-toggle
class="ml-2"
[color]="'primary'"
[formControlName]="'meetups'"
#meetupsToggle
>
</mat-slide-toggle>
</div>
</div>
<!-- Divider -->
<div class="my-10 border-t"></div>
<!-- Section -->
<div class="w-full text-xl">Account Activity</div>
<div class="mt-8 w-full font-medium">Email me when:</div>
<div class="mt-4 grid w-full grid-cols-1 gap-4">
<!-- Comments -->
<div class="flex items-center justify-between">
<div
class="flex-auto cursor-pointer leading-6"
(click)="comments.toggle()"
>
someone comments on one of my items
</div>
<mat-slide-toggle
class="ml-2"
[color]="'primary'"
[formControlName]="'comments'"
#comments
>
</mat-slide-toggle>
</div>
<!-- Mention --> <!-- Mention -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div <div class="flex-auto cursor-pointer" (click)="mentionToggle.toggle()">
class="flex-auto cursor-pointer leading-6" <div class="font-medium leading-6">Mention</div>
(click)="mention.toggle()" <div class="text-secondary text-md">Receive notifications when someone mentions you.</div>
>
someone mentions me
</div> </div>
<mat-slide-toggle <mat-slide-toggle class="ml-2" [color]="'primary'" [formControlName]="'mention'" #mentionToggle></mat-slide-toggle>
class="ml-2"
[color]="'primary'"
[formControlName]="'mention'"
#mention
>
</mat-slide-toggle>
</div> </div>
<!-- Follow -->
<!-- Private Message -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div <div class="flex-auto cursor-pointer" (click)="privateMessageToggle.toggle()">
class="flex-auto cursor-pointer leading-6" <div class="font-medium leading-6">Private Message</div>
(click)="follow.toggle()" <div class="text-secondary text-md">Receive notifications for private messages.</div>
>
someone follows me
</div> </div>
<mat-slide-toggle <mat-slide-toggle class="ml-2" [color]="'primary'" [formControlName]="'privateMessage'" #privateMessageToggle></mat-slide-toggle>
class="ml-2"
[color]="'primary'"
[formControlName]="'follow'"
#follow
>
</mat-slide-toggle>
</div> </div>
<!-- Inquiry -->
<!-- Zap -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div <div class="flex-auto cursor-pointer" (click)="zapToggle.toggle()">
class="flex-auto cursor-pointer leading-6" <div class="font-medium leading-6">Zap</div>
(click)="inquiry.toggle()" <div class="text-secondary text-md">Receive notifications when you get a zap.</div>
>
someone replies to my job posting
</div> </div>
<mat-slide-toggle <mat-slide-toggle class="ml-2" [color]="'primary'" [formControlName]="'zap'" #zapToggle></mat-slide-toggle>
class="ml-2" </div>
[color]="'primary'"
[formControlName]="'inquiry'" <!-- New Follower -->
#inquiry <div class="flex items-center justify-between">
> <div class="flex-auto cursor-pointer" (click)="followerToggle.toggle()">
</mat-slide-toggle> <div class="font-medium leading-6">New Follower</div>
<div class="text-secondary text-md">Receive notifications when someone follows you.</div>
</div>
<mat-slide-toggle class="ml-2" [color]="'primary'" [formControlName]="'follower'" #followerToggle></mat-slide-toggle>
</div> </div>
</div> </div>
<!-- Divider -->
<div class="my-10 border-t"></div> <div class="my-10 border-t"></div>
<!-- Actions --> <!-- Actions -->
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<button mat-stroked-button type="button">Cancel</button> <button mat-stroked-button type="button">Cancel</button>
<button <button class="ml-4" mat-flat-button type="button" [color]="'primary'" (click)="saveSettings()">Save</button>
class="ml-4"
mat-flat-button
type="button"
[color]="'primary'"
>
Save
</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -28,29 +28,53 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
}) })
export class SettingsNotificationsComponent implements OnInit { export class SettingsNotificationsComponent implements OnInit {
notificationsForm: UntypedFormGroup; notificationsForm: UntypedFormGroup;
notificationKinds: { [key: string]: number } = {
mention: 1,
privateMessage: 4,
zap: 9735,
follower: 3,
};
/**
* Constructor
*/
constructor(private _formBuilder: UntypedFormBuilder) {} constructor(private _formBuilder: UntypedFormBuilder) {}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void { ngOnInit(): void {
// Create the form const savedSettings = this.loadNotificationSettings();
this.notificationsForm = this._formBuilder.group({ this.notificationsForm = this._formBuilder.group({
communication: [true], mention: [savedSettings.includes(this.notificationKinds.mention)],
security: [true], privateMessage: [savedSettings.includes(this.notificationKinds.privateMessage)],
meetups: [false], zap: [savedSettings.includes(this.notificationKinds.zap)],
comments: [false], follower: [savedSettings.includes(this.notificationKinds.follower)],
mention: [true],
follow: [true],
inquiry: [true],
}); });
} }
saveSettings(): void {
const formValues = this.notificationsForm.value;
const enabledKinds: number[] = [];
if (formValues.mention) {
enabledKinds.push(this.notificationKinds.mention);
}
if (formValues.privateMessage) {
enabledKinds.push(this.notificationKinds.privateMessage);
}
if (formValues.zap) {
enabledKinds.push(this.notificationKinds.zap);
}
if (formValues.follower) {
enabledKinds.push(this.notificationKinds.follower);
}
this.saveNotificationSettings(enabledKinds);
console.log('Notification settings saved:', enabledKinds);
}
private saveNotificationSettings(settings: number[]): void {
localStorage.setItem('notificationSettings', JSON.stringify(settings));
}
private loadNotificationSettings(): number[] {
const storedSettings = localStorage.getItem('notificationSettings');
return storedSettings ? JSON.parse(storedSettings) : [1, 3, 4, 9735]; // Default to all kinds if not set
}
} }

View File

@@ -9,26 +9,14 @@ export class NavigationService {
private _navigation: ReplaySubject<Navigation> = private _navigation: ReplaySubject<Navigation> =
new ReplaySubject<Navigation>(1); new ReplaySubject<Navigation>(1);
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for navigation
*/
get navigation$(): Observable<Navigation> { get navigation$(): Observable<Navigation> {
return this._navigation.asObservable(); return this._navigation.asObservable();
} }
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get all navigation data
*/
get(): Observable<Navigation> { get(): Observable<Navigation> {
return this._httpClient.get<Navigation>('api/common/navigation').pipe( return this._httpClient.get<Navigation>('api/navigation').pipe(
tap((navigation) => { tap((navigation) => {
this._navigation.next(navigation); this._navigation.next(navigation);
}) })

View File

@@ -6,11 +6,11 @@ import {
defaultNavigation, defaultNavigation,
futuristicNavigation, futuristicNavigation,
horizontalNavigation, horizontalNavigation,
} from 'app/mock-api/common/navigation/data'; } from 'app/layout/navigation/data';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class NavigationMockApi { export class NavigationApi {
private readonly _compactNavigation: AngorNavigationItem[] = private readonly _compactNavigation: AngorNavigationItem[] =
compactNavigation; compactNavigation;
private readonly _defaultNavigation: AngorNavigationItem[] = private readonly _defaultNavigation: AngorNavigationItem[] =
@@ -20,26 +20,13 @@ export class NavigationMockApi {
private readonly _horizontalNavigation: AngorNavigationItem[] = private readonly _horizontalNavigation: AngorNavigationItem[] =
horizontalNavigation; horizontalNavigation;
/**
* Constructor
*/
constructor(private _angorMockApiService: AngorMockApiService) { constructor(private _angorMockApiService: AngorMockApiService) {
// Register Mock API handlers // Register Mock API handlers
this.registerHandlers(); this.registerHandlers();
} }
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register Mock API handlers
*/
registerHandlers(): void { registerHandlers(): void {
// ----------------------------------------------------------------------------------------------------- this._angorMockApiService.onGet('api/navigation').reply(() => {
// @ Navigation - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService.onGet('api/common/navigation').reply(() => {
// Fill compact navigation children using the default navigation // Fill compact navigation children using the default navigation
this._compactNavigation.forEach((compactNavItem) => { this._compactNavigation.forEach((compactNavItem) => {
this._defaultNavigation.forEach((defaultNavItem) => { this._defaultNavigation.forEach((defaultNavItem) => {

View File

@@ -0,0 +1,5 @@
import { NavigationApi } from "./api";
export const navigationServices = [
NavigationApi,
];

View File

@@ -1,160 +0,0 @@
import { Injectable } from '@angular/core';
import { AngorMockApiService } from '@angor/lib/mock-api';
import {
chats as chatsData,
contacts as contactsData,
messages as messagesData,
profile as profileData,
} from 'app/mock-api/apps/chat/data';
import { assign, cloneDeep, omit } from 'lodash-es';
@Injectable({ providedIn: 'root' })
export class ChatMockApi {
private _chats: any[] = chatsData;
private _contacts: any[] = contactsData;
private _messages: any[] = messagesData;
private _profile: any = profileData;
/**
* Constructor
*/
constructor(private _angorMockApiService: AngorMockApiService) {
// Register Mock API handlers
this.registerHandlers();
// Modify the chats array to attach certain data to it
this._chats = this._chats.map((chat) => ({
...chat,
// Get the actual contact object from the id and attach it to the chat
contact: this._contacts.find(
(contact) => contact.id === chat.contactId
),
// Since we use same set of messages on all chats, we assign them here.
messages: this._messages.map((message) => ({
...message,
chatId: chat.id,
contactId:
message.contactId === 'me'
? this._profile.id
: chat.contactId,
isMine: message.contactId === 'me',
})),
}));
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register Mock API handlers
*/
registerHandlers(): void {
// -----------------------------------------------------------------------------------------------------
// @ Chats - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService.onGet('api/apps/chat/chats').reply(() => {
// Clone the chats
const chats = cloneDeep(this._chats);
// Return the response
return [200, chats];
});
// -----------------------------------------------------------------------------------------------------
// @ Chat - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onGet('api/apps/chat/chat')
.reply(({ request }) => {
// Get the chat id
const id = request.params.get('id');
// Clone the chats
const chats = cloneDeep(this._chats);
// Find the chat we need
const chat = chats.find((item) => item.id === id);
// Return the response
return [200, chat];
});
// -----------------------------------------------------------------------------------------------------
// @ Chat - PATCH
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onPatch('api/apps/chat/chat')
.reply(({ request }) => {
// Get the id and chat
const id = request.body.id;
const chat = cloneDeep(request.body.chat);
// Prepare the updated chat
let updatedChat = null;
// Find the chat and update it
this._chats.forEach((item, index, chats) => {
if (item.id === id) {
// Update the chat
chats[index] = assign({}, chats[index], chat);
// Store the updated chat
updatedChat = chats[index];
}
});
// Return the response
return [200, updatedChat];
});
// -----------------------------------------------------------------------------------------------------
// @ Contacts - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService.onGet('api/apps/chat/contacts').reply(() => {
// Clone the contacts
let contacts = cloneDeep(this._contacts);
// Sort the contacts by the name field by default
contacts.sort((a, b) => a.name.localeCompare(b.name));
// Omit details and attachments from contacts
contacts = contacts.map((contact) =>
omit(contact, ['details', 'attachments'])
);
// Return the response
return [200, contacts];
});
// -----------------------------------------------------------------------------------------------------
// @ Contact Details - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onGet('api/apps/chat/contact')
.reply(({ request }) => {
// Get the contact id
const id = request.params.get('id');
// Clone the contacts
const contacts = cloneDeep(this._contacts);
// Find the contact
const contact = contacts.find((item) => item.id === id);
// Return the response
return [200, contact];
});
// -----------------------------------------------------------------------------------------------------
// @ Profile - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService.onGet('api/apps/chat/profile').reply(() => {
// Clone the profile
const profile = cloneDeep(this._profile);
// Return the response
return [200, profile];
});
}
}

View File

@@ -1,266 +0,0 @@
/* eslint-disable */
import { DateTime } from 'luxon';
/* Get the current instant */
const now = DateTime.now();
/**
* Attachments are common and will be filled from here
* to keep the demo data maintainable.
*/
const _attachments = {
media: [
'images/cards/01-320x200.jpg',
'images/cards/02-320x200.jpg',
'images/cards/03-320x200.jpg',
'images/cards/04-320x200.jpg',
'images/cards/05-320x200.jpg',
'images/cards/06-320x200.jpg',
'images/cards/07-320x200.jpg',
'images/cards/08-320x200.jpg',
],
docs: [],
links: [],
};
/**
* If a message belongs to our user, it's marked by setting it as
* 'me'. If it belongs to the user we are chatting with, then it
* left empty. We will be using this same conversation for each chat
* to keep things more maintainable for the demo.
*/
export const messages = [
{
id: 'e6b2b82f-b199-4a60-9696-5f3e40d2715d',
contactId: 'me',
value: 'Hi!',
createdAt: now.minus({ week: 1 }).set({ hour: 18, minute: 56 }).toISO(),
},
{
id: 'eb82cf4b-fa93-4bf4-a88a-99e987ddb7ea',
contactId: '',
value: 'Hey, dude!',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 4 }).toISO(),
},
{
id: '3cf9b2a6-ae54-47db-97b2-ee139a8f84e5',
contactId: '',
value: 'Long time no see.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 4 }).toISO(),
},
{
id: '2ab91b0f-fafb-45f3-88df-7efaff29134b',
contactId: 'me',
value: 'Yeah, man... Things were quite busy for me and my family.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 6 }).toISO(),
},
{
id: '10e81481-378f-49ac-b06b-7c59dcc639ae',
contactId: '',
value: "What's up? Anything I can help with?",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 6 }).toISO(),
},
{
id: '3b334e72-6605-4ebd-a4f6-3850067048de',
contactId: 'me',
value: "We've been on the move, changed 3 places over 4 months.",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 7 }).toISO(),
},
{
id: '25998113-3a96-4dd0-a7b9-4d2bb58db3f3',
contactId: '',
value: "Wow! That's crazy! 🤯 What happened?",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 7 }).toISO(),
},
{
id: '30adb3da-0e4f-487e-aec2-6d9f31e097f6',
contactId: 'me',
value: 'You know I got a job in that big software company. First move was because of that.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(),
},
{
id: 'c0d6fd6e-d294-4845-8751-e84b8f2c4d3b',
contactId: 'me',
value: 'Then they decided to re-locate me after a month.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(),
},
{
id: '8d3c442b-62fa-496f-bffa-210ff5c1866b',
contactId: 'me',
value: 'It was a pain since we just settled in, house, kids school, etc.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(),
},
{
id: '3cf26ef0-e81f-4698-ac39-487454413332',
contactId: 'me',
value: 'So we moved again.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 9 }).toISO(),
},
{
id: '415151b9-9ee9-40a4-a4ad-2d88146bc71b',
contactId: '',
value: "It's crazy!",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 9 }).toISO(),
},
{
id: 'd6f29648-c85c-4dfb-a6ff-6b7ebc40c993',
contactId: 'me',
value: 'Then the virus happened, and we went remote after moving again.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 10 }).toISO(),
},
{
id: '5329c20d-6754-47ec-af8c-660c72be3528',
contactId: 'me',
value: "So we moved back to the first location, the third time!",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 10 }).toISO(),
},
{
id: '26f2ccbf-aef7-4b49-88df-f6b59381110a',
contactId: '',
value: "Ohh dude, that's tough in such a short period.",
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(),
},
{
id: 'ea7662d5-7b72-4c19-ad6c-f80320541001',
contactId: '',
value: '😕',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(),
},
{
id: '3a2d3a0e-839b-46e7-86ae-ca0826ecda7c',
contactId: 'me',
value: 'Thanks! It was great catching up.',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(),
},
{
id: '562e3524-15b7-464a-bbf6-9b2582e5e0ee',
contactId: '',
value: 'Yeah! Lets grab a coffee next week, remotely!',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(),
},
{
id: '9269c775-bad5-46e1-b33b-2de8704ec1d6',
contactId: 'me',
value: 'Sure! See you next week!',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(),
},
{
id: '779a27f2-bece-41c6-b9ca-c422570aee68',
contactId: '',
value: 'See you!',
createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(),
},
{
id: 'bab8ca0e-b8e5-4375-807b-1c91fca25a5d',
contactId: 'me',
value: 'Hey! Available now? Lets grab that coffee, remotely! :)',
createdAt: now.set({ hour: 12, minute: 45 }).toISO(),
},
{
id: '8445a84d-599d-4e2d-a31c-5f4f29ad2b4c',
contactId: '',
value: 'Hi!',
createdAt: now.set({ hour: 12, minute: 56 }).toISO(),
},
{
id: '9f506742-50da-4350-af9d-61e53392fa08',
contactId: '',
value: "Sure! I'll call you in 5, okay?",
createdAt: now.set({ hour: 12, minute: 56 }).toISO(),
},
{
id: 'ca8523d8-faed-45f7-af09-f6bd5c3f3875',
contactId: 'me',
value: 'Awesome! Call me in 5 minutes.',
createdAt: now.set({ hour: 12, minute: 58 }).toISO(),
},
{
id: '39944b00-1ffe-4ffb-8ca6-13c292812e06',
contactId: '',
value: '👍🏻',
createdAt: now.set({ hour: 13, minute: 0 }).toISO(),
},
];
export const chats = [
{
id: 'ff6bc7f1-449a-4419-af62-b89ce6cae0aa',
contactId: '9d3f0e7f-dcbd-4e56-a5e8-87b8154e9edf',
unreadCount: 2,
muted: false,
lastMessage: 'See you tomorrow!',
lastMessageAt: '26/04/2021',
},
{
id: '4459a3f0-b65e-4df2-8c37-6ec72fcc4b31',
contactId: '16b9e696-ea95-4dd8-86c4-3caf705a1dc6',
unreadCount: 0,
muted: false,
lastMessage: 'See you tomorrow!',
lastMessageAt: '26/04/2021',
}
];
export const contacts = [
{
id: '16b9e696-ea95-4dd8-86c4-3caf705a1dc6',
avatar: 'images/avatars/avatar-placeholder.png',
name: 'Sali',
about: "Hi there! I'm using AngorChat.",
details: {
emails: [
{
email: 'nunezfaulkner@mail.tv',
label: 'Personal',
},
],
phoneNumbers: [
{
country: 'xk',
phoneNumber: '909 552 3327',
label: 'Mobile',
},
],
title: 'Hotel Manager',
company: 'Buzzopia',
birthday: '1982-01-23T12:00:00.000Z',
address: '614 Herkimer Court, Darrtown, Nebraska, PO9308',
},
attachments: _attachments,
},
{
id: '9d3f0e7f-dcbd-4e56-a5e8-87b8154e9edf',
avatar: 'images/avatars/avatar-placeholder.png',
name: 'John',
about: "Hi there! I'm using AngorChat.",
details: {
emails: [
{
email: 'bernardlangley@mail.com',
label: 'Personal',
},
{
email: 'langley.bernard@boilcat.name',
label: 'Work',
},
],
phoneNumbers: [
{
country: 'md',
phoneNumber: '893 548 2862',
label: 'Mobile',
},
],
title: 'Electromedical Equipment Technician',
company: 'Boilcat',
birthday: '1988-05-26T12:00:00.000Z',
address: '943 Adler Place, Hamilton, South Dakota, PO5592',
},
attachments: _attachments,
}
];
export const profile: any = {
id: 'cfaad35d-07a3-4447-a6c3-d8c3d54fd5df',
name: 'Username',
email: 'username@angor.io',
avatar: 'images/avatars/avatar-placeholder.png',
about: "Hi there! I'm using AngorChat.",
};

View File

@@ -1,161 +0,0 @@
import { Injectable } from '@angular/core';
import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api';
import { notifications as notificationsData } from 'app/mock-api/common/notifications/data';
import { assign, cloneDeep } from 'lodash-es';
@Injectable({ providedIn: 'root' })
export class NotificationsMockApi {
private _notifications: any = notificationsData;
/**
* Constructor
*/
constructor(private _angorMockApiService: AngorMockApiService) {
// Register Mock API handlers
this.registerHandlers();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register Mock API handlers
*/
registerHandlers(): void {
// -----------------------------------------------------------------------------------------------------
// @ Notifications - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onGet('api/common/notifications')
.reply(() => [200, cloneDeep(this._notifications)]);
// -----------------------------------------------------------------------------------------------------
// @ Notifications - POST
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onPost('api/common/notifications')
.reply(({ request }) => {
// Get the notification
const newNotification = cloneDeep(request.body.notification);
// Generate a new GUID
newNotification.id = AngorMockApiUtils.guid();
// Unshift the new notification
this._notifications.unshift(newNotification);
// Return the response
return [200, newNotification];
});
// -----------------------------------------------------------------------------------------------------
// @ Notifications - PATCH
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onPatch('api/common/notifications')
.reply(({ request }) => {
// Get the id and notification
const id = request.body.id;
const notification = cloneDeep(request.body.notification);
// Prepare the updated notification
let updatedNotification = null;
// Find the notification and update it
this._notifications.forEach(
(item: any, index: number, notifications: any[]) => {
if (item.id === id) {
// Update the notification
notifications[index] = assign(
{},
notifications[index],
notification
);
// Store the updated notification
updatedNotification = notifications[index];
}
}
);
// Return the response
return [200, updatedNotification];
});
// -----------------------------------------------------------------------------------------------------
// @ Notifications - DELETE
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onDelete('api/common/notifications')
.reply(({ request }) => {
// Get the id
const id = request.params.get('id');
// Prepare the deleted notification
let deletedNotification = null;
// Find the notification
const index = this._notifications.findIndex(
(item: any) => item.id === id
);
// Store the deleted notification
deletedNotification = cloneDeep(this._notifications[index]);
// Delete the notification
this._notifications.splice(index, 1);
// Return the response
return [200, deletedNotification];
});
// -----------------------------------------------------------------------------------------------------
// @ Mark all as read - GET
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onGet('api/common/notifications/mark-all-as-read')
.reply(() => {
// Go through all notifications
this._notifications.forEach(
(item: any, index: number, notifications: any[]) => {
// Mark it as read
notifications[index].read = true;
notifications[index].seen = true;
}
);
// Return the response
return [200, true];
});
// -----------------------------------------------------------------------------------------------------
// @ Toggle read status - POST
// -----------------------------------------------------------------------------------------------------
this._angorMockApiService
.onPost('api/common/notifications/toggle-read-status')
.reply(({ request }) => {
// Get the notification
const notification = cloneDeep(request.body.notification);
// Prepare the updated notification
let updatedNotification = null;
// Find the notification and update it
this._notifications.forEach(
(item: any, index: number, notifications: any[]) => {
if (item.id === notification.id) {
// Update the notification
notifications[index].read = notification.read;
// Store the updated notification
updatedNotification = notifications[index];
}
}
);
// Return the response
return [200, updatedNotification];
});
}
}

View File

@@ -1,96 +0,0 @@
/* eslint-disable */
import { DateTime } from 'luxon';
/* Retrieve the current date and time */
const currentMoment = DateTime.now();
export const notifications = [
{
id: '493190c9-5b61-4912-afe5-78c21f1044d7',
icon: 'heroicons_mini:star',
title: 'Daily Challenges',
description: 'Your submission has been approved',
time: currentMoment.minus({ minute: 25 }).toISO(), // 25 minutes ago
read: false,
},
{
id: '6e3e97e5-effc-4fb7-b730-52a151f0b641',
image: 'images/avatars/avatar-placeholder.png',
description:
'<strong>Leo Gill</strong> has added you to the <em>Top Secret Project</em> group and assigned you as the <em>Project Manager</em>',
time: currentMoment.minus({ minute: 50 }).toISO(), // 50 minutes ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
{
id: 'b91ccb58-b06c-413b-b389-87010e03a120',
icon: 'heroicons_mini:envelope',
title: 'Mailbox',
description: 'You have 15 unread emails across 3 mailboxes',
time: currentMoment.minus({ hour: 3 }).toISO(), // 3 hours ago
read: false,
link: '/dashboards/project',
useRouter: true,
},
{
id: '541416c9-84a7-408a-8d74-27a43c38d797',
icon: 'heroicons_mini:arrow-path',
title: 'Cron Jobs',
description: 'Your <em>Docker container</em> is ready for publishing',
time: currentMoment.minus({ hour: 5 }).toISO(), // 5 hours ago
read: false,
link: '/dashboards/project',
useRouter: true,
},
{
id: 'ef7b95a7-8e8b-4616-9619-130d9533add9',
image: 'images/avatars/avatar-placeholder.png',
description:
'<strong>Roger Murray</strong> has accepted your friend request',
time: currentMoment.minus({ hour: 7 }).toISO(), // 7 hours ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
{
id: 'eb8aa470-635e-461d-88e1-23d9ea2a5665',
image: 'images/avatars/avatar-placeholder.png',
description: '<strong>Sophie Stone</strong> sent you a direct message',
time: currentMoment.minus({ hour: 9 }).toISO(), // 9 hours ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
{
id: 'b85c2338-cc98-4140-bbf8-c226ce4e395e',
icon: 'heroicons_mini:envelope',
title: 'Mailbox',
description: 'You have 3 new unread emails',
time: currentMoment.minus({ day: 1 }).toISO(), // 1 day ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
{
id: '8f8e1bf9-4661-4939-9e43-390957b60f42',
icon: 'heroicons_mini:star',
title: 'Daily Challenges',
description:
'Your submission has been accepted, and you can now sign up for the final assignment, which will be available in 2 days',
time: currentMoment.minus({ day: 3 }).toISO(), // 3 days ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
{
id: '30af917b-7a6a-45d1-822f-9e7ad7f8bf69',
icon: 'heroicons_mini:arrow-path',
title: 'Cron Jobs',
description: 'Your Vagrant container is ready for download',
time: currentMoment.minus({ day: 4 }).toISO(), // 4 days ago
read: true,
link: '/dashboards/project',
useRouter: true,
},
];

View File

@@ -1,11 +0,0 @@
import { ChatMockApi } from 'app/mock-api/apps/chat/api';
import { NavigationMockApi } from 'app/mock-api/common/navigation/api';
import { NotificationsMockApi } from 'app/mock-api/common/notifications/api';
export const mockApiServices = [
ChatMockApi,
NavigationMockApi,
NotificationsMockApi,
];

View File

@@ -57,7 +57,12 @@ export class NotificationService {
return this.notificationCount.asObservable(); return this.notificationCount.asObservable();
} }
public async subscribeToNotifications(pubkey: string): Promise<void> { private loadFilterPreferences(): number[] {
const storedPreferences = localStorage.getItem('notificationSettings');
return storedPreferences ? JSON.parse(storedPreferences) : [1, 3, 4, 9735]; // Default to all kinds if not set
}
public async subscribeToNotifications(pubkey: string): Promise<void> {
await this.relayService.ensureConnectedRelays(); await this.relayService.ensureConnectedRelays();
const pool = this.relayService.getPool(); const pool = this.relayService.getPool();
const connectedRelays = this.relayService.getConnectedRelays(); const connectedRelays = this.relayService.getConnectedRelays();
@@ -66,9 +71,14 @@ export class NotificationService {
throw new Error('No connected relays'); throw new Error('No connected relays');
} }
const lastNotificationTimestamp = this.loadTimestampFromLocalStorage(); const lastNotificationTimestamp = this.loadTimestampFromLocalStorage();
const filterPreferences = this.loadFilterPreferences();
if (filterPreferences.length === 0) {
filterPreferences.push(1, 3, 4, 9735);
}
const filter: Filter = { const filter: Filter = {
kinds: [1, 3, 4, 9735], kinds: filterPreferences,
'#p': [pubkey], '#p': [pubkey],
limit: 50, limit: 50,
since: lastNotificationTimestamp || undefined since: lastNotificationTimestamp || undefined