Rework folder structure in frontend
Pages are now grouped per group they appear in on in the header (where possible) Utilities used by multiple pages in the project are grouped in folders such as services/pipes/etc
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<button type="button" class="button-outer" (click)="copyIpToClipboard()">
|
||||
<span class="button-inner">{{ displayText }}</span>
|
||||
</button>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CopyIpComponent } from './copy-ip.component';
|
||||
|
||||
describe('CopyIpComponent', () => {
|
||||
let component: CopyIpComponent;
|
||||
let fixture: ComponentFixture<CopyIpComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CopyIpComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CopyIpComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-copy-ip',
|
||||
templateUrl: './copy-ip.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
styleUrl: './copy-ip.component.scss'
|
||||
})
|
||||
export class CopyIpComponent {
|
||||
public displayText: string = 'Copy IP'
|
||||
|
||||
public copyIpToClipboard(): void {
|
||||
navigator.clipboard.writeText('play.alttd.com');
|
||||
this.displayText = 'Copied!';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<h2 mat-dialog-title>Login</h2>
|
||||
<div mat-dialog-content>
|
||||
<form [formGroup]="loginForm">
|
||||
<mat-form-field appearance="fill" style="width: 100%">
|
||||
<mat-label>Enter your code</mat-label>
|
||||
<input matInput formControlName="code" type="text">
|
||||
<mat-error *ngIf="formHasError()">
|
||||
Code is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onCancel()">Cancel</button>
|
||||
<button mat-flat-button color="primary" (click)="onSubmit()" [disabled]="!loginForm.valid">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
padding: 16px 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle} from '@angular/material/dialog';
|
||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {AuthService} from '@services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatInputModule,
|
||||
MatFormFieldModule,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
export class LoginDialogComponent {
|
||||
public loginForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<LoginDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
private authService: AuthService,
|
||||
private snackBar: MatSnackBar
|
||||
) {
|
||||
this.loginForm = this.fb.group({
|
||||
code: ['', [
|
||||
Validators.required,
|
||||
Validators.minLength(8),
|
||||
Validators.maxLength(8),
|
||||
Validators.pattern('^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]+$')
|
||||
]]
|
||||
});
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (!this.loginForm.valid) {
|
||||
this.snackBar.open('Invalid code', '', {duration: 2000});
|
||||
return;
|
||||
}
|
||||
this.snackBar.open('Logging in...', '', {duration: 2000});
|
||||
this.authService.login(this.loginForm.value.code).subscribe({
|
||||
next: (jwt) => {
|
||||
this.dialogRef.close(jwt);
|
||||
},
|
||||
error: () => {
|
||||
this.loginForm.get('code')?.setErrors({
|
||||
invalid: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public formHasError() {
|
||||
return this.loginForm.get('code')?.hasError('required');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="switch-div" style="z-index: 1;">
|
||||
<label class="switch">
|
||||
<input type="checkbox" [checked]="isDarkMode" (change)="toggleTheme()" id="slider">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.switch-div {
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 37px;
|
||||
width: 37px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
box-shadow: 0 0 15px #2020203d;
|
||||
background: var(--switch) url('https://i.ibb.co/7JfqXxB/sunny.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--link-color);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px var(--link-color);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(24px);
|
||||
-ms-transform: translateX(24px);
|
||||
transform: translateX(24px);
|
||||
background: var(--switch) url('https://i.ibb.co/FxzBYR9/night.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ThemeComponent} from './theme.component';
|
||||
|
||||
describe('ThemeComponent', () => {
|
||||
let component: ThemeComponent;
|
||||
let fixture: ComponentFixture<ThemeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ThemeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ThemeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ThemeService} from './theme.service';
|
||||
import {THEME_MODE} from '@custom-types/constant';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
],
|
||||
selector: 'app-theme',
|
||||
templateUrl: './theme.component.html',
|
||||
styleUrl: './theme.component.scss'
|
||||
})
|
||||
export class ThemeComponent implements OnInit, OnDestroy {
|
||||
isDarkMode: boolean = false;
|
||||
private themeSubscription: Subscription | null = null;
|
||||
|
||||
constructor(private themeService: ThemeService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.themeSubscription = this.themeService.theme$.subscribe(theme => {
|
||||
this.isDarkMode = theme === THEME_MODE.DARK;
|
||||
});
|
||||
}
|
||||
|
||||
toggleTheme(): void {
|
||||
this.isDarkMode = this.themeService.toggleTheme() === THEME_MODE.DARK;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.themeSubscription) {
|
||||
this.themeSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ThemeService} from './theme.service';
|
||||
|
||||
describe('ThemeService', () => {
|
||||
let service: ThemeService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ThemeService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {CookieService} from 'ngx-cookie-service';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {THEME_MODE} from '@custom-types/constant';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ThemeService {
|
||||
private readonly THEME_KEY = 'theme';
|
||||
|
||||
private themeSubject: BehaviorSubject<THEME_MODE> = new BehaviorSubject<THEME_MODE>(THEME_MODE.LIGHT);
|
||||
public theme$ = this.themeSubject.asObservable();
|
||||
|
||||
constructor(private cookieService: CookieService) {
|
||||
this.initializeTheme();
|
||||
}
|
||||
|
||||
public initializeTheme(): void {
|
||||
const savedTheme = this.cookieService.check(this.THEME_KEY)
|
||||
? this.cookieService.get(this.THEME_KEY)
|
||||
: THEME_MODE.DARK;
|
||||
let currentTheme: THEME_MODE;
|
||||
switch (savedTheme) {
|
||||
case THEME_MODE.DARK: {
|
||||
currentTheme = THEME_MODE.DARK;
|
||||
break;
|
||||
}
|
||||
case THEME_MODE.LIGHT:
|
||||
default: {
|
||||
currentTheme = THEME_MODE.LIGHT;
|
||||
}
|
||||
}
|
||||
this.setTheme(currentTheme);
|
||||
}
|
||||
|
||||
public setTheme(themeName: THEME_MODE): void {
|
||||
this.cookieService.set(this.THEME_KEY, themeName, 365);
|
||||
document.body.className = themeName;
|
||||
this.themeSubject.next(themeName);
|
||||
}
|
||||
|
||||
public toggleTheme(): THEME_MODE {
|
||||
const currentTheme = this.themeSubject.getValue();
|
||||
const newTheme = currentTheme === THEME_MODE.LIGHT ? THEME_MODE.DARK : THEME_MODE.LIGHT;
|
||||
this.setTheme(newTheme);
|
||||
return newTheme
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user