,
5 | ) => {
6 | const item = arr.find(predicate);
7 |
8 | if (!item) {
9 | return;
10 | }
11 |
12 | Object.assign(item, object);
13 | };
14 |
--------------------------------------------------------------------------------
/src/styles/_bootstrap-globals.scss:
--------------------------------------------------------------------------------
1 | @import 'functions', 'variables', 'mixins';
2 |
3 | $theme-colors: (
4 | 'primary': #1266f1,
5 | 'secondary': #b23cfd,
6 | 'success': #00b74a,
7 | 'danger': #f93154,
8 | 'warning': #ffa900,
9 | 'info': #39c0ed,
10 | 'light': #fbfbfb,
11 | 'dark': #262626,
12 | );
13 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": ["src/main.ts", "src/polyfills.ts"],
9 | "include": ["src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": ["jasmine"]
7 | },
8 | "files": ["src/test.ts", "src/polyfills.ts"],
9 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": true,
3 | "singleQuote": true,
4 | "printWidth": 80,
5 | "trailingComma": "all",
6 | "tabWidth": 2,
7 | "arrowParens": "avoid",
8 | "overrides": [
9 | {
10 | "files": "**/*.md",
11 | "options": {
12 | "parser": "markdown",
13 | "proseWrap": "always"
14 | }
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.19.0-alpine AS builder
2 | WORKDIR /app
3 | COPY package.json /app
4 | COPY yarn.lock /app
5 | RUN yarn install --frozen-lockfile
6 | COPY . .
7 | RUN yarn run build
8 |
9 | FROM nginx:alpine
10 | COPY --from=builder /app/nginx.conf /etc/nginx/templates/default.conf.conf
11 | COPY --from=builder /app/dist/task /usr/share/nginx/html
12 |
13 | EXPOSE 80
--------------------------------------------------------------------------------
/src/app/shared/components/confirm-dialog/confirm-dialog.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 | {{ description }}
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { MainSocket } from './socket/main-socket';
4 | import { ErrorDialogInterceptor } from './interceptor/error-dialog.interceptor';
5 |
6 | @NgModule({
7 | imports: [CommonModule],
8 | providers: [MainSocket, ErrorDialogInterceptor],
9 | })
10 | export class CoreModule {}
11 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch(err => console.error(err));
14 |
--------------------------------------------------------------------------------
/src/app/shared/services/sound.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { SoundService } from './sound.service';
4 |
5 | describe('SoundService', () => {
6 | let service: SoundService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(SoundService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | api: 'https://nest-auth.ubbly.club/api',
4 | socket: 'https://nest-auth.ubbly.club/',
5 | apps: {
6 | facebook: '535472397651204',
7 | google:
8 | '331672215174-0hlpm8fhjphiou05ovsd82vglor401ct.apps.googleusercontent.com',
9 | apple: {
10 | clientId: 'nest-auth.ubbly.club',
11 | redirectUri: 'https://nest-auth.ubbly.club/',
12 | },
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Angular client for NestJS Authentication (Messages, Rooms, Login, Register, Google Login, Facebook Login, Apple Login)
2 |
3 | To use this client, get the [server](https://github.com/DenzelCode/nest-auth) up and running.
4 |
5 | Try it out now! [Demo](https://nest-auth.ubbly.club/)
6 |
7 | It also contains a websocket implementation of the authentication too :D
8 |
9 | 
10 |
--------------------------------------------------------------------------------
/src/app/shared/services/sound.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | export enum Sound {
4 | Message = 'message-tone',
5 | }
6 |
7 | @Injectable({
8 | providedIn: 'root',
9 | })
10 | export class SoundService {
11 | constructor() {}
12 |
13 | playSound(sound: Sound) {
14 | const path = `assets/tones/${sound}.mp3`;
15 |
16 | if (sound) {
17 | const audio = new Audio(path);
18 | audio.muted = false;
19 | audio.play();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/features/notification/notification.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { SharedModule } from '../../shared/shared.module';
4 | import { PushNotificationComponent } from './components/push-notification/push-notification.component';
5 |
6 | @NgModule({
7 | declarations: [PushNotificationComponent],
8 | imports: [CommonModule, SharedModule],
9 | exports: [PushNotificationComponent],
10 | })
11 | export class NotificationModule {}
12 |
--------------------------------------------------------------------------------
/src/app/features/room/components/join-room-dialog/join-room-dialog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Join
3 |
15 |
16 |
--------------------------------------------------------------------------------
/src/app/shared/components/error-dialog/error-dialog.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 | {{ message[0] }}
4 |
5 |
6 |
7 | -
8 | {{ msg }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "downlevelIteration": true,
10 | "experimentalDecorators": true,
11 | "resolveJsonModule": true,
12 | "moduleResolution": "node",
13 | "importHelpers": true,
14 | "target": "es2015",
15 | "module": "es2020",
16 | "lib": ["es2018", "dom"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/styles/styles.scss:
--------------------------------------------------------------------------------
1 | /* Bootstrap */
2 | @import 'bootstrap';
3 |
4 | body {
5 | background-color: #f3f3f3;
6 | }
7 |
8 | .full-width {
9 | width: 100%;
10 | }
11 |
12 | mat-card-footer {
13 | margin: 5px;
14 | }
15 |
16 | .position-absolute {
17 | position: absolute !important;
18 | z-index: 3;
19 | }
20 |
21 | .spacer {
22 | flex: 1 1 auto;
23 | }
24 |
25 | .flex-grow {
26 | flex: 1 1 0;
27 | }
28 |
29 | body,
30 | html,
31 | app-root,
32 | ng-component {
33 | display: flex;
34 | flex-direction: column;
35 | flex-grow: 1;
36 | height: 100%;
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/features/messages/pages/direct-message-page/direct-message-page.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chatting with {{ to.username }} ({{ to.online ? 'Online' : 'Offline' }})
5 |
6 |
7 | logout
8 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/features/messages/messages.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { MessagesComponent } from './components/messages/messages.component';
4 | import { SharedModule } from '../../shared/shared.module';
5 | import { DirectMessagePageComponent } from './pages/direct-message-page/direct-message-page.component';
6 |
7 | @NgModule({
8 | declarations: [MessagesComponent, DirectMessagePageComponent],
9 | imports: [CommonModule, SharedModule],
10 | exports: [MessagesComponent, DirectMessagePageComponent],
11 | })
12 | export class MessagesModule {}
13 |
--------------------------------------------------------------------------------
/src/app/features/main/main.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | Welcome, {{ user.username }}!
12 |
13 |
14 |
Rooms
15 |
Settings
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ngsw-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json",
3 | "index": "/index.html",
4 | "assetGroups": [
5 | {
6 | "name": "app",
7 | "installMode": "prefetch",
8 | "resources": {
9 | "files": [
10 | "/favicon.ico",
11 | "/index.html",
12 | "/manifest.webmanifest",
13 | "/*.css",
14 | "/*.js"
15 | ]
16 | }
17 | },
18 | {
19 | "name": "assets",
20 | "installMode": "lazy",
21 | "updateMode": "prefetch",
22 | "resources": {
23 | "files": [
24 | "/assets/**",
25 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
26 | ]
27 | }
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/features/room/components/room-item/room-item.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { RoomItemComponent } from './room-item.component';
4 |
5 | describe('RoomItemComponent', () => {
6 | let component: RoomItemComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ RoomItemComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(RoomItemComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/shared/components/error-dialog/error-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, OnInit } from '@angular/core';
2 | import { MAT_DIALOG_DATA } from '@angular/material/dialog';
3 |
4 | export interface ErrorDialogData {
5 | title: string;
6 | message: string | string[];
7 | }
8 |
9 | @Component({
10 | templateUrl: './error-dialog.component.html',
11 | styleUrls: ['./error-dialog.component.scss'],
12 | })
13 | export class ErrorDialogComponent implements OnInit {
14 | title: ErrorDialogData['title'];
15 | message: ErrorDialogData['message'];
16 |
17 | constructor(@Inject(MAT_DIALOG_DATA) data: ErrorDialogData) {
18 | this.title = data.title || 'Error';
19 | this.message =
20 | data.message instanceof Array ? data.message : [data.message || ''];
21 | }
22 |
23 | ngOnInit(): void {}
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
48 | .vscode
--------------------------------------------------------------------------------
/src/app/features/user/service/recover.service.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { environment } from '../../../../environments/environment';
4 |
5 | const { api } = environment;
6 |
7 | export interface ChangePasswordBody {
8 | password: string;
9 | confirmPassword: string;
10 | }
11 |
12 | @Injectable({
13 | providedIn: 'root',
14 | })
15 | export class RecoverService {
16 | constructor(private http: HttpClient) {}
17 |
18 | recoverPassword(email: string) {
19 | return this.http.post(`${api}/recover`, { email });
20 | }
21 |
22 | validateCode(code: string) {
23 | return this.http.get(`${api}/recover/${code}`);
24 | }
25 |
26 | changePassword(code: string, body: ChangePasswordBody) {
27 | return this.http.post(`${api}/recover/${code}`, body);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/features/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { SettingsPageComponent } from './pages/settings-page/settings-page.component';
4 | import { SharedModule } from '../../shared/shared.module';
5 | import { RecoverPageComponent } from './pages/recover-page/recover-page.component';
6 | import { RecoverChangePasswordPageComponent } from './pages/recover-change-password-page/recover-change-password-page.component';
7 |
8 | @NgModule({
9 | declarations: [
10 | SettingsPageComponent,
11 | RecoverPageComponent,
12 | RecoverChangePasswordPageComponent,
13 | ],
14 | imports: [CommonModule, SharedModule],
15 | exports: [
16 | SettingsPageComponent,
17 | RecoverPageComponent,
18 | RecoverChangePasswordPageComponent,
19 | ],
20 | })
21 | export class UserModule {}
22 |
--------------------------------------------------------------------------------
/src/app/features/room/components/upsert-room-dialog/upsert-room-dialog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Create Room
3 |
24 |
25 |
26 |
27 | Update Room {{ room.title }}
28 |
29 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: {
11 | context(
12 | path: string,
13 | deep?: boolean,
14 | filter?: RegExp,
15 | ): {
16 | keys(): string[];
17 | (id: string): T;
18 | };
19 | };
20 |
21 | // First, initialize the Angular testing environment.
22 | getTestBed().initTestEnvironment(
23 | BrowserDynamicTestingModule,
24 | platformBrowserDynamicTesting(),
25 | );
26 | // Then we find all the tests.
27 | const context = require.context('./', true, /\.spec\.ts$/);
28 | // And load the modules.
29 | context.keys().map(context);
30 |
--------------------------------------------------------------------------------
/src/app/features/main/main.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, OnInit } from '@angular/core';
2 | import { Subject } from 'rxjs';
3 | import { takeUntil } from 'rxjs/operators';
4 | import { AuthService, User } from 'src/app/features/auth/service/auth.service';
5 |
6 | @Component({
7 | templateUrl: './main.component.html',
8 | styleUrls: ['./main.component.scss'],
9 | })
10 | export class MainComponent implements OnInit, OnDestroy {
11 | user: User;
12 |
13 | destroy$ = new Subject();
14 |
15 | constructor(private authService: AuthService) {}
16 |
17 | ngOnInit() {
18 | this.authService.user$
19 | .pipe(takeUntil(this.destroy$))
20 | .subscribe(user => (this.user = user));
21 | }
22 |
23 | ngOnDestroy() {
24 | this.destroy$.next();
25 | this.destroy$.complete();
26 | }
27 |
28 | logout() {
29 | this.authService.logout();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CodeAuth
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |