From 1acda7978e10b031b8eaa957ace7f3bf91cbdc10 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 20 Mar 2025 12:33:27 +0000 Subject: [PATCH] Added user management for groups. Improved user experience slightly. Added some error checks for request acks. --- .../action-item-edit.component.html | 3 +- .../action-item-edit.component.ts | 20 ++++- src/app/auth/tts-login/tts-login.component.ts | 15 ++-- .../auth/user-card/user-card.component.html | 3 +- .../group-item-edit.component.html | 2 +- .../group-item-edit.component.ts | 8 +- .../group-item/group-item.component.html | 9 ++- .../groups/group-item/group-item.component.ts | 9 +-- .../group-page/group-page.component.html | 15 +++- .../groups/group-page/group-page.component.ts | 13 ++- src/app/groups/groups/groups.component.html | 14 ++-- src/app/groups/groups/groups.component.ts | 7 ++ src/app/hermes-client.service.ts | 31 ++++++- .../policy-item-edit.component.html | 2 +- .../policy-item-edit.component.ts | 16 +++- .../redemption-item-edit.component.ts | 18 ++--- src/app/shared/utils/groups.ts | 1 + src/app/shared/utils/string-compare.ts | 17 ++++ .../twitch-user-item-add.component.html | 23 ++++++ .../twitch-user-item-add.component.scss | 0 .../twitch-user-item-add.component.spec.ts | 23 ++++++ .../twitch-user-item-add.component.ts | 81 +++++++++++++++++++ .../twitch-user-item.component.html | 7 ++ .../twitch-user-item.component.scss | 18 +++++ .../twitch-user-item.component.spec.ts | 23 ++++++ .../twitch-user-item.component.ts | 39 +++++++++ .../twitch-user-list.component.html | 28 +++++++ .../twitch-user-list.component.scss | 38 +++++++++ .../twitch-user-list.component.spec.ts | 23 ++++++ .../twitch-user-list.component.ts | 68 ++++++++++++++++ src/app/twitch-users/twitch-users.module.ts | 19 +++++ .../users/user-item/user-item.component.html | 1 + .../users/user-item/user-item.component.scss | 0 .../user-item/user-item.component.spec.ts | 23 ++++++ .../users/user-item/user-item.component.ts | 11 +++ .../users/user-list/user-list.component.html | 1 + .../users/user-list/user-list.component.scss | 0 .../user-list/user-list.component.spec.ts | 23 ++++++ .../users/user-list/user-list.component.ts | 11 +++ src/app/users/users.module.ts | 12 +++ 40 files changed, 623 insertions(+), 52 deletions(-) create mode 100644 src/app/shared/utils/groups.ts create mode 100644 src/app/shared/utils/string-compare.ts create mode 100644 src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.html create mode 100644 src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.scss create mode 100644 src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.spec.ts create mode 100644 src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.ts create mode 100644 src/app/twitch-users/twitch-user-item/twitch-user-item.component.html create mode 100644 src/app/twitch-users/twitch-user-item/twitch-user-item.component.scss create mode 100644 src/app/twitch-users/twitch-user-item/twitch-user-item.component.spec.ts create mode 100644 src/app/twitch-users/twitch-user-item/twitch-user-item.component.ts create mode 100644 src/app/twitch-users/twitch-user-list/twitch-user-list.component.html create mode 100644 src/app/twitch-users/twitch-user-list/twitch-user-list.component.scss create mode 100644 src/app/twitch-users/twitch-user-list/twitch-user-list.component.spec.ts create mode 100644 src/app/twitch-users/twitch-user-list/twitch-user-list.component.ts create mode 100644 src/app/twitch-users/twitch-users.module.ts create mode 100644 src/app/users/user-item/user-item.component.html create mode 100644 src/app/users/user-item/user-item.component.scss create mode 100644 src/app/users/user-item/user-item.component.spec.ts create mode 100644 src/app/users/user-item/user-item.component.ts create mode 100644 src/app/users/user-list/user-list.component.html create mode 100644 src/app/users/user-list/user-list.component.scss create mode 100644 src/app/users/user-list/user-list.component.spec.ts create mode 100644 src/app/users/user-list/user-list.component.ts create mode 100644 src/app/users/users.module.ts diff --git a/src/app/actions/action-item-edit/action-item-edit.component.html b/src/app/actions/action-item-edit/action-item-edit.component.html index a9627a6..943a770 100644 --- a/src/app/actions/action-item-edit/action-item-edit.component.html +++ b/src/app/actions/action-item-edit/action-item-edit.component.html @@ -108,7 +108,8 @@ } - + @if (!isNew) { + @for (group of specialGroups; track $index) { + @if (!exists(group)) { - - - - + (click)="openDialog(group)">{{group[0].toUpperCase() + group.substring(1)}} Group + } + } \ No newline at end of file diff --git a/src/app/groups/groups/groups.component.ts b/src/app/groups/groups/groups.component.ts index 603b8e0..567ebbe 100644 --- a/src/app/groups/groups/groups.component.ts +++ b/src/app/groups/groups/groups.component.ts @@ -12,6 +12,7 @@ import { GroupItemEditComponent } from '../group-item-edit/group-item-edit.compo import { MatMenuModule } from '@angular/material/menu'; import { HermesClientService } from '../../hermes-client.service'; import { GroupChatter } from '../../shared/models/group-chatter'; +import { SpecialGroups } from '../../shared/utils/groups'; @Component({ selector: 'groups', @@ -31,6 +32,8 @@ export class GroupsComponent { private readonly _client = inject(HermesClientService); private readonly _route = inject(ActivatedRoute); private readonly _dialog = inject(MatDialog); + + readonly specialGroups = SpecialGroups; items: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = []; @@ -116,4 +119,8 @@ export class GroupsComponent { compare(a: Group, b: Group) { return a.name.localeCompare(b.name); } + + exists(groupName: string) { + return this.items.some(g => g.group.name == groupName); + } } \ No newline at end of file diff --git a/src/app/hermes-client.service.ts b/src/app/hermes-client.service.ts index 0d3230a..dcfaf4c 100644 --- a/src/app/hermes-client.service.ts +++ b/src/app/hermes-client.service.ts @@ -14,10 +14,11 @@ export interface Message { providedIn: 'root' }) export class HermesClientService { - pipe = new DatePipe('en-US'); + private readonly pipe = new DatePipe('en-US'); session_id: string | undefined; connected: boolean; logged_in: boolean; + api_key: string | undefined; constructor(private socket: HermesSocketService, private events: EventService) { this.connected = false; @@ -40,6 +41,7 @@ export class HermesClientService { this.connected = false; this.logged_in = false; this.session_id = undefined; + this.api_key = undefined; this.socket.close(); this.events.emit('tts_logoff', null); } @@ -78,6 +80,8 @@ export class HermesClientService { if (this.logged_in) return; + this.api_key = api_key; + this.send(1, { api_key, web_login: true, @@ -97,6 +101,17 @@ export class HermesClientService { }); } + public createGroupChatter(groupId: string, chatterId: string, chatterLabel: string) { + if (!this.logged_in) + return; + + this.send(3, { + request_id: null, + type: "create_group_chatter", + data: { group: groupId, chatter: chatterId, label: chatterLabel }, + }); + } + public createPolicy(groupId: string, path: string, usage: number, timespan: number) { if (!this.logged_in) return; @@ -157,6 +172,17 @@ export class HermesClientService { }); } + public deleteGroupChatter(groupId: string, chatterId: string) { + if (!this.logged_in) + return; + + this.send(3, { + request_id: null, + type: "delete_group_chatter", + data: { group: groupId, chatter: chatterId }, + }); + } + public deletePolicy(id: string) { if (!this.logged_in) return; @@ -363,6 +389,9 @@ export class HermesClientService { console.log("TTS Heartbeat received. Potential connection problem?"); break; case 2: // Login Ack + if (message.d.another_client) { + return; + } console.log("TTS Login successful."); this.logged_in = true; this.session_id = message.d.session_id; diff --git a/src/app/policies/policy-item-edit/policy-item-edit.component.html b/src/app/policies/policy-item-edit/policy-item-edit.component.html index c14b041..e6e24f4 100644 --- a/src/app/policies/policy-item-edit/policy-item-edit.component.html +++ b/src/app/policies/policy-item-edit/policy-item-edit.component.html @@ -51,7 +51,7 @@ } - + @if (isNew) { + + + \ No newline at end of file diff --git a/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.scss b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.spec.ts b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.spec.ts new file mode 100644 index 0000000..cae0cd3 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TwitchUserItemAddComponent } from './twitch-user-item-add.component'; + +describe('TwitchUserItemAddComponent', () => { + let component: TwitchUserItemAddComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TwitchUserItemAddComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TwitchUserItemAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.ts b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.ts new file mode 100644 index 0000000..fea2b21 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.ts @@ -0,0 +1,81 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, inject, OnInit } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ActionItemEditComponent } from '../../actions/action-item-edit/action-item-edit.component'; +import { HermesClientService } from '../../hermes-client.service'; +import { MatCardModule } from '@angular/material/card'; +import { MatButtonModule } from '@angular/material/button'; +import { Group } from '../../shared/models/group'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { group } from 'console'; + +@Component({ + selector: 'app-twitch-user-item-add', + imports: [ + MatButtonModule, + MatCardModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + ], + templateUrl: './twitch-user-item-add.component.html', + styleUrl: './twitch-user-item-add.component.scss' +}) +export class TwitchUserItemAddComponent implements OnInit { + private readonly client = inject(HermesClientService); + private readonly data = inject<{ username: string, group: Group }>(MAT_DIALOG_DATA); + private readonly http = inject(HttpClient); + + readonly usernameControl = new FormControl('', [Validators.required]); + readonly dialogRef = inject(MatDialogRef); + + waitForResponse = false; + + + ngOnInit(): void { + this.usernameControl.setValue(this.data.username); + } + + submit() { + if (this.usernameControl.invalid || this.waitForResponse || !this.client.api_key) { + return; + } + + this.waitForResponse = true; + + const username = this.usernameControl.value!.toLowerCase(); + this.http.get('/api/auth/twitch/users?login=' + username, { + headers: { + 'x-api-key': this.client.api_key, + } + }) + .subscribe((response: any) => { + if (!response.user) { + this.waitForResponse = false; + return; + } + + if (!response.user) { + return; + } + + this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_group_chatter' && d.d.request.data.chatter == response.user.id) + .subscribe({ + next: (d) => { + if (d.d.error) { + // TODO: update & show response error message. + } else { + this.dialogRef.close(d.d.data); + } + }, + error: () => this.waitForResponse = false, + complete: () => this.waitForResponse = false, + }); + this.client.createGroupChatter(this.data.group.id, response.user.id, response.user.login) + }); + } +} diff --git a/src/app/twitch-users/twitch-user-item/twitch-user-item.component.html b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.html new file mode 100644 index 0000000..96799d1 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.html @@ -0,0 +1,7 @@ +
+ +

{{user.chatter_label}}

+
\ No newline at end of file diff --git a/src/app/twitch-users/twitch-user-item/twitch-user-item.component.scss b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.scss new file mode 100644 index 0000000..3936820 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.scss @@ -0,0 +1,18 @@ +div { + padding: 0.3em; +} + +p { + position: relative; + top: -7px; + margin-left: 5px; + display: inline; +} + +.mat-icon { + color: #C83838; +} + +.mat-icon:hover { + color: red; +} \ No newline at end of file diff --git a/src/app/twitch-users/twitch-user-item/twitch-user-item.component.spec.ts b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.spec.ts new file mode 100644 index 0000000..2f3e638 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TwitchUserItemComponent } from './twitch-user-item.component'; + +describe('TwitchUserItemComponent', () => { + let component: TwitchUserItemComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TwitchUserItemComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TwitchUserItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/twitch-users/twitch-user-item/twitch-user-item.component.ts b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.ts new file mode 100644 index 0000000..c7f1ce4 --- /dev/null +++ b/src/app/twitch-users/twitch-user-item/twitch-user-item.component.ts @@ -0,0 +1,39 @@ +import { Component, inject, Input } from '@angular/core'; +import { GroupChatter } from '../../shared/models/group-chatter'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { HermesClientService } from '../../hermes-client.service'; +import EventService from '../../shared/services/EventService'; + +@Component({ + selector: 'twitch-user-item', + imports: [ + MatButtonModule, + MatIconModule, + MatInputModule + ], + templateUrl: './twitch-user-item.component.html', + styleUrl: './twitch-user-item.component.scss' +}) +export class TwitchUserItemComponent { + @Input({ required: true }) user: GroupChatter = { chatter_id: -1, chatter_label: '', user_id: '', group_id: '' }; + + private readonly _client = inject(HermesClientService); + private readonly _events = inject(EventService); + private _deleted = false; + + delete() { + if (this._deleted) + return; + + this._deleted = true; + + this._client.first(d => d.d.request.type == 'delete_group_chatter' && d.d.request.data.group == this.user.group_id && d.d.request.data.chatter == this.user.chatter_id) + .subscribe(async (response) => { + console.log('delete group chatter', response) + this._events.emit('delete_group_chatter', this.user); + }); + this._client.deleteGroupChatter(this.user.group_id, this.user.chatter_id.toString()); + } +} diff --git a/src/app/twitch-users/twitch-user-list/twitch-user-list.component.html b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.html new file mode 100644 index 0000000..aa3c0e2 --- /dev/null +++ b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.html @@ -0,0 +1,28 @@ +
    +
  • + + Filter + + + + +
  • + @for (user of users; track $index) { +
  • + +
  • + } + @if (!users.length) { + @if (searchControl.value) { +

    No users fits the filter.

    + } @else { +

    No users in this group.

    + } + } +
\ No newline at end of file diff --git a/src/app/twitch-users/twitch-user-list/twitch-user-list.component.scss b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.scss new file mode 100644 index 0000000..e3e4013 --- /dev/null +++ b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.scss @@ -0,0 +1,38 @@ +@use '@angular/material' as mat; + +ul { + @include mat.all-component-densities(-5); + + @include mat.form-field-overrides(( + outlined-outline-color: rgb(167, 88, 199), + outlined-focus-label-text-color: rgb(155, 57, 194), + outlined-focus-outline-color: rgb(155, 57, 194), + )); + + background-color: rgb(202, 68, 255); + border-radius: 15px; + margin: 0 0; + padding: 0; + max-width: 500px; + overflow: hidden; +} + +ul li { + margin: 0; + padding: 0; + list-style: none; + background-color: rgb(240, 165, 255); +} + +ul li.header { + background-color: rgb(215, 115, 255); + display: flex; + align-items: center; + justify-content: space-around; + flex-direction: row; + padding: 8px; +} + +ul .notice { + text-align: center; +} \ No newline at end of file diff --git a/src/app/twitch-users/twitch-user-list/twitch-user-list.component.spec.ts b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.spec.ts new file mode 100644 index 0000000..d37ac81 --- /dev/null +++ b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TwitchUserListComponent } from './twitch-user-list.component'; + +describe('TwitchUserListComponent', () => { + let component: TwitchUserListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TwitchUserListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TwitchUserListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/twitch-users/twitch-user-list/twitch-user-list.component.ts b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.ts new file mode 100644 index 0000000..1ff276c --- /dev/null +++ b/src/app/twitch-users/twitch-user-list/twitch-user-list.component.ts @@ -0,0 +1,68 @@ +import { Component, inject, Input } from '@angular/core'; +import { GroupChatter } from '../../shared/models/group-chatter'; +import { TwitchUserItemComponent } from "../twitch-user-item/twitch-user-item.component"; +import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { containsLettersInOrder } from '../../shared/utils/string-compare'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; +import { HermesClientService } from '../../hermes-client.service'; +import { Group } from '../../shared/models/group'; +import { TwitchUserItemAddComponent } from '../twitch-user-item-add/twitch-user-item-add.component'; +import EventService from '../../shared/services/EventService'; + +@Component({ + selector: 'twitch-user-list', + imports: [ + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + ReactiveFormsModule, + TwitchUserItemComponent, + ], + templateUrl: './twitch-user-list.component.html', + styleUrl: './twitch-user-list.component.scss' +}) +export class TwitchUserListComponent { + @Input({ required: true }) twitchUsers: GroupChatter[] = []; + @Input() group: Group | undefined; + + readonly dialog = inject(MatDialog); + readonly client = inject(HermesClientService); + readonly events = inject(EventService); + readonly searchControl: FormControl = new FormControl(''); + + opened = false; + + constructor() { + this.events.listen('delete_group_chatter', (chatter: GroupChatter) => { + this.twitchUsers.splice(this.twitchUsers.findIndex(c => c.group_id == chatter.group_id && c.chatter_id == chatter.chatter_id), 1); + }); + } + + get users(): GroupChatter[] { + return this.twitchUsers.filter(u => containsLettersInOrder(u.chatter_label, this.searchControl.value)); + } + + add() { + if (this.opened) + return; + + this.opened = true; + + const dialogRef = this.dialog.open(TwitchUserItemAddComponent, { + data: { username: this.searchControl.value, group: this.group }, + }); + + dialogRef.afterClosed().subscribe((chatter: GroupChatter) => { + this.opened = false; + if (!chatter) + return; + + this.twitchUsers.push(chatter); + }); + } +} diff --git a/src/app/twitch-users/twitch-users.module.ts b/src/app/twitch-users/twitch-users.module.ts new file mode 100644 index 0000000..ab62a89 --- /dev/null +++ b/src/app/twitch-users/twitch-users.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TwitchUserItemComponent } from './twitch-user-item/twitch-user-item.component'; +import { TwitchUserListComponent } from './twitch-user-list/twitch-user-list.component'; + + + +@NgModule({ + declarations: [], + exports: [ + TwitchUserItemComponent, + TwitchUserListComponent, + ], + imports: [ + TwitchUserItemComponent, + TwitchUserListComponent, + ] +}) +export class TwitchUsersModule { } diff --git a/src/app/users/user-item/user-item.component.html b/src/app/users/user-item/user-item.component.html new file mode 100644 index 0000000..fcca67a --- /dev/null +++ b/src/app/users/user-item/user-item.component.html @@ -0,0 +1 @@ +

user-item works!

diff --git a/src/app/users/user-item/user-item.component.scss b/src/app/users/user-item/user-item.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/users/user-item/user-item.component.spec.ts b/src/app/users/user-item/user-item.component.spec.ts new file mode 100644 index 0000000..92cb024 --- /dev/null +++ b/src/app/users/user-item/user-item.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserItemComponent } from './user-item.component'; + +describe('UserItemComponent', () => { + let component: UserItemComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserItemComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UserItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/users/user-item/user-item.component.ts b/src/app/users/user-item/user-item.component.ts new file mode 100644 index 0000000..72efc8c --- /dev/null +++ b/src/app/users/user-item/user-item.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-user-item', + imports: [], + templateUrl: './user-item.component.html', + styleUrl: './user-item.component.scss' +}) +export class UserItemComponent { + +} diff --git a/src/app/users/user-list/user-list.component.html b/src/app/users/user-list/user-list.component.html new file mode 100644 index 0000000..16029d7 --- /dev/null +++ b/src/app/users/user-list/user-list.component.html @@ -0,0 +1 @@ +

user-list works!

diff --git a/src/app/users/user-list/user-list.component.scss b/src/app/users/user-list/user-list.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/users/user-list/user-list.component.spec.ts b/src/app/users/user-list/user-list.component.spec.ts new file mode 100644 index 0000000..e452eae --- /dev/null +++ b/src/app/users/user-list/user-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserListComponent } from './user-list.component'; + +describe('UserListComponent', () => { + let component: UserListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UserListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/users/user-list/user-list.component.ts b/src/app/users/user-list/user-list.component.ts new file mode 100644 index 0000000..fdc18bd --- /dev/null +++ b/src/app/users/user-list/user-list.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-user-list', + imports: [], + templateUrl: './user-list.component.html', + styleUrl: './user-list.component.scss' +}) +export class UserListComponent { + +} diff --git a/src/app/users/users.module.ts b/src/app/users/users.module.ts new file mode 100644 index 0000000..b6d597e --- /dev/null +++ b/src/app/users/users.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + + + +@NgModule({ + declarations: [], + imports: [ + CommonModule + ] +}) +export class UsersModule { }