-
-
Hello, {{ title }}
-
Congratulations! Your app is running. 🎉
-
-
+
+
+
+
{{ title }}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ house
+ Dashboard
+
+
+
+
+ cloud_upload
+ Upload Game
+
+
+
+
+ account_circle
+ Account
+
+
+
+
+
+ logout
+ Logout
+
+
+
+
+
+
+
+
-
diff --git a/frontend/indiestream/src/app/app.component.scss b/frontend/indiestream/src/app/app.component.scss
index e69de29..7c4eb1a 100644
--- a/frontend/indiestream/src/app/app.component.scss
+++ b/frontend/indiestream/src/app/app.component.scss
@@ -0,0 +1,33 @@
+.left-toolbar {
+ display: flex;
+ align-items: center;
+}
+
+.right-toolbar {
+ padding-right: 1rem;
+ display: flex;
+ align-items: center;
+ }
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.sidenav-container {
+ position: fixed;
+ height: 100%;
+ width: 100%;
+}
+
+.sidenav-content {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+}
+
+.user-avatar {
+ border-radius: 50%;
+ width: 44px;
+ height: 44px;
+}
diff --git a/frontend/indiestream/src/app/app.component.spec.ts b/frontend/indiestream/src/app/app.component.spec.ts
index 8680c1e..12070d9 100644
--- a/frontend/indiestream/src/app/app.component.spec.ts
+++ b/frontend/indiestream/src/app/app.component.spec.ts
@@ -1,11 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
+import {OAuthService} from "angular-oauth2-oidc";
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [AppComponent, BrowserAnimationsModule],
+ imports: [AppComponent, BrowserAnimationsModule, OAuthService],
}).compileComponents();
});
@@ -15,16 +16,16 @@ describe('AppComponent', () => {
expect(app).toBeTruthy();
});
- it(`should have the 'indiestream' title`, () => {
+ it(`should have the 'indiegamestream' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
- expect(app.title).toEqual('indiestream');
+ expect(app.title).toEqual('IndieGameStream');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
- expect(compiled.querySelector('h1')?.textContent).toContain('Hello, indiestream');
+ expect(compiled.querySelector('h1')?.textContent).toContain('IndieGameStream');
});
});
diff --git a/frontend/indiestream/src/app/app.component.ts b/frontend/indiestream/src/app/app.component.ts
index 9e810ce..e10ac0a 100644
--- a/frontend/indiestream/src/app/app.component.ts
+++ b/frontend/indiestream/src/app/app.component.ts
@@ -1,23 +1,58 @@
-import { Component, OnInit } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-import { GamesService } from "./services/games.service";
-import { Game } from "./modules/games";
-import { CommonModule } from "@angular/common";
+import {Component, ViewChild} from '@angular/core';
+import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
+import {CommonModule, NgOptimizedImage} from "@angular/common";
import { MatButtonModule } from '@angular/material/button';
import { HttpClientModule } from "@angular/common/http";
-import { GameUploadComponent} from "./components/game-upload/game-upload.component";
-import {GamesOverviewComponent} from "./components/games-overview/games-overview.component";
+import { GameUploadComponent } from "./components/game-upload/game-upload.component";
+import { GamesOverviewComponent } from "./components/games-overview/games-overview.component";
+import { MatToolbar } from "@angular/material/toolbar";
+import { MatIcon } from "@angular/material/icon";
+import { MatSidenav, MatSidenavContainer, MatSidenavContent } from '@angular/material/sidenav';
+import { MatListItem, MatNavList } from "@angular/material/list";
+import { Location } from '@angular/common';
+import {AuthService} from "./services/auth.service";
+import {OAuthService} from "angular-oauth2-oidc";
@Component({
selector: 'app-root',
standalone: true,
- imports: [RouterOutlet, CommonModule, MatButtonModule, HttpClientModule, GameUploadComponent, GamesOverviewComponent],
+ imports: [
+ RouterOutlet,
+ CommonModule,
+ MatButtonModule,
+ HttpClientModule,
+ GameUploadComponent,
+ GamesOverviewComponent,
+ MatToolbar,
+ MatIcon,
+ MatSidenav,
+ MatSidenavContainer,
+ MatNavList,
+ MatListItem,
+ MatSidenavContent,
+ MatSidenav,
+ MatSidenavContainer,
+ RouterLink,
+ RouterLinkActive,
+ NgOptimizedImage
+ ],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
- title = 'indiestream';
+ title = 'IndieGameStream';
status: string = '';
+ @ViewChild(MatSidenav)
+ sidenav!: MatSidenav;
+ uncollapsed = true;
- constructor() { }
+ constructor(public authService: AuthService, private oAuthService: OAuthService) { }
+
+ toggleMenu() {
+ this.uncollapsed = !this.uncollapsed;
+ }
+
+ logout() {
+ this.authService.logout();
+ }
}
diff --git a/frontend/indiestream/src/app/app.config.ts b/frontend/indiestream/src/app/app.config.ts
index 89fc8e3..9e9c0c8 100644
--- a/frontend/indiestream/src/app/app.config.ts
+++ b/frontend/indiestream/src/app/app.config.ts
@@ -8,6 +8,7 @@ import { provideAnimations } from "@angular/platform-browser/animations";
import { provideOAuthClient } from "angular-oauth2-oidc";
import { AuthInitializer } from "./services/auth.initializer";
import { AuthInterceptor } from "./services/authInterceptor.service";
+import {AuthGuard} from "./guards/auth.guard";
export const appConfig: ApplicationConfig = {
providers: [
@@ -20,5 +21,7 @@ export const appConfig: ApplicationConfig = {
useFactory: (authInitializer: AuthInitializer) => () =>
authInitializer.initializeApp(),
deps: [AuthInitializer],
- multi: true, },]
+ multi: true, },
+ AuthGuard,
+ ]
};
diff --git a/frontend/indiestream/src/app/app.routes.ts b/frontend/indiestream/src/app/app.routes.ts
index dc39edb..8dd4872 100644
--- a/frontend/indiestream/src/app/app.routes.ts
+++ b/frontend/indiestream/src/app/app.routes.ts
@@ -1,3 +1,32 @@
-import { Routes } from '@angular/router';
+import { Route } from '@angular/router';
+import {GameUploadComponent} from "./components/game-upload/game-upload.component";
+import {GamesOverviewComponent} from "./components/games-overview/games-overview.component";
+import {AccountComponent} from "./components/account/account.component";
+import {LandingPageComponent} from "./components/landing-page/landing-page.component";
+import {AuthGuard} from "./guards/auth.guard";
-export const routes: Routes = [];
+export const routes: Route[] = [
+ {
+ path: '',
+ component: LandingPageComponent,
+ },
+ {
+ path: 'dashboard',
+ component: GamesOverviewComponent,
+ canActivate: [AuthGuard],
+ },
+ {
+ path: 'upload',
+ component: GameUploadComponent,
+ canActivate: [AuthGuard],
+ },
+ {
+ path: 'account',
+ component: AccountComponent,
+ canActivate: [AuthGuard],
+ },
+ {
+ path: '**',
+ redirectTo: '',
+ },
+];
diff --git a/frontend/indiestream/src/app/components/account/account.component.html b/frontend/indiestream/src/app/components/account/account.component.html
new file mode 100644
index 0000000..2d5fa18
--- /dev/null
+++ b/frontend/indiestream/src/app/components/account/account.component.html
@@ -0,0 +1 @@
+
account works!
diff --git a/frontend/indiestream/src/app/components/account/account.component.scss b/frontend/indiestream/src/app/components/account/account.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/indiestream/src/app/components/account/account.component.spec.ts b/frontend/indiestream/src/app/components/account/account.component.spec.ts
new file mode 100644
index 0000000..07d869b
--- /dev/null
+++ b/frontend/indiestream/src/app/components/account/account.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AccountComponent } from './account.component';
+
+describe('AccountComponent', () => {
+ let component: AccountComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AccountComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(AccountComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/indiestream/src/app/components/account/account.component.ts b/frontend/indiestream/src/app/components/account/account.component.ts
new file mode 100644
index 0000000..d13db0c
--- /dev/null
+++ b/frontend/indiestream/src/app/components/account/account.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-account',
+ standalone: true,
+ imports: [],
+ templateUrl: './account.component.html',
+ styleUrl: './account.component.scss'
+})
+export class AccountComponent {
+
+}
diff --git a/frontend/indiestream/src/app/components/games-overview/games-overview.component.html b/frontend/indiestream/src/app/components/games-overview/games-overview.component.html
index 327e207..105e2e1 100644
--- a/frontend/indiestream/src/app/components/games-overview/games-overview.component.html
+++ b/frontend/indiestream/src/app/components/games-overview/games-overview.component.html
@@ -1,33 +1,27 @@
-
-
-
- | ID |
- {{game.id}} |
-
-
- Title |
- {{game.title}} |
-
-
- Status |
- {{game.status}} |
-
-
- URL |
- {{game.url}} |
-
-
- Refresh |
- |
-
-
- Delete |
- |
-
+Hi {{authService.getName()}}
-
-
-
+
+
+
info
+
You have not uploaded any games yet :/
+
+
+
+
+
+ {{ game.title }}
+ Game ID: {{ game.id }}
+
+
+ Status: {{ game.status }}
+ URL: {{ game.url }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/indiestream/src/app/components/games-overview/games-overview.component.scss b/frontend/indiestream/src/app/components/games-overview/games-overview.component.scss
index 68a3ccf..880607a 100644
--- a/frontend/indiestream/src/app/components/games-overview/games-overview.component.scss
+++ b/frontend/indiestream/src/app/components/games-overview/games-overview.component.scss
@@ -1,4 +1,8 @@
.games-table {
white-space: pre;
- width: 80%;
+ width: 100%;
+}
+
+.no-games-warning {
+ padding-bottom: 4rem;
}
diff --git a/frontend/indiestream/src/app/components/games-overview/games-overview.component.ts b/frontend/indiestream/src/app/components/games-overview/games-overview.component.ts
index 522d248..45469e0 100644
--- a/frontend/indiestream/src/app/components/games-overview/games-overview.component.ts
+++ b/frontend/indiestream/src/app/components/games-overview/games-overview.component.ts
@@ -1,6 +1,6 @@
import {Component, OnInit} from '@angular/core';
-import {MatButton} from "@angular/material/button";
-import {NgForOf} from "@angular/common";
+import {MatButton, MatFabButton} from "@angular/material/button";
+import {NgForOf, NgIf} from "@angular/common";
import {GamesService} from "../../services/games.service";
import {Game} from "../../modules/games";
import {
@@ -14,34 +14,55 @@ import {
MatTable
} from "@angular/material/table";
import {HttpClientModule} from "@angular/common/http";
+import {AuthService} from "../../services/auth.service";
+import {MatIcon} from "@angular/material/icon";
+import {RouterLink} from "@angular/router";
+import {
+ MatCard,
+ MatCardActions,
+ MatCardContent,
+ MatCardHeader,
+ MatCardSubtitle,
+ MatCardTitle
+} from "@angular/material/card";
+import {OAuthService} from "angular-oauth2-oidc";
@Component({
selector: 'app-games-overview',
standalone: true,
- imports: [
- MatButton,
- NgForOf,
- MatTable,
- MatColumnDef,
- MatHeaderCell,
- MatCell,
- MatHeaderRow,
- MatRow,
- MatRowDef,
- MatHeaderRowDef,
- MatCellDef,
- MatHeaderCellDef,
- HttpClientModule,
- ],
+ imports: [
+ MatButton,
+ NgForOf,
+ MatTable,
+ MatColumnDef,
+ MatHeaderCell,
+ MatCell,
+ MatHeaderRow,
+ MatRow,
+ MatRowDef,
+ MatHeaderRowDef,
+ MatCellDef,
+ MatHeaderCellDef,
+ HttpClientModule,
+ NgIf,
+ MatIcon,
+ RouterLink,
+ MatCard,
+ MatCardTitle,
+ MatCardSubtitle,
+ MatCardHeader,
+ MatCardContent,
+ MatCardActions,
+ MatFabButton,
+ ],
templateUrl: './games-overview.component.html',
styleUrl: './games-overview.component.scss'
})
export class GamesOverviewComponent implements OnInit
{
- columnsToDisplay = ['ID', 'title', 'status', 'url', 'refresh', 'delete'];
- public games: any;
+ public games: Game[] = [];
- constructor(private gamesService: GamesService) {
+ constructor(private gamesService: GamesService, public authService: AuthService, private oAuthService: OAuthService) {
}
@@ -65,10 +86,15 @@ export class GamesOverviewComponent implements OnInit
)
}
+ refreshAllGames() {
+ for (let game of this.games) {
+ this.refreshGame(game.id);
+ }
+ }
+
deleteGame(id: string) {
this.gamesService.deleteGame(id);
//TODO refresh?
//getGames():
}
-
}
diff --git a/frontend/indiestream/src/app/components/landing-page/landing-page.component.html b/frontend/indiestream/src/app/components/landing-page/landing-page.component.html
new file mode 100644
index 0000000..8d1873e
--- /dev/null
+++ b/frontend/indiestream/src/app/components/landing-page/landing-page.component.html
@@ -0,0 +1 @@
+
landing-page works!
diff --git a/frontend/indiestream/src/app/components/landing-page/landing-page.component.scss b/frontend/indiestream/src/app/components/landing-page/landing-page.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/indiestream/src/app/components/landing-page/landing-page.component.spec.ts b/frontend/indiestream/src/app/components/landing-page/landing-page.component.spec.ts
new file mode 100644
index 0000000..70960d7
--- /dev/null
+++ b/frontend/indiestream/src/app/components/landing-page/landing-page.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LandingPageComponent } from './landing-page.component';
+
+describe('LandingPageComponent', () => {
+ let component: LandingPageComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LandingPageComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LandingPageComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/indiestream/src/app/components/landing-page/landing-page.component.ts b/frontend/indiestream/src/app/components/landing-page/landing-page.component.ts
new file mode 100644
index 0000000..1d65bdb
--- /dev/null
+++ b/frontend/indiestream/src/app/components/landing-page/landing-page.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-landing-page',
+ standalone: true,
+ imports: [],
+ templateUrl: './landing-page.component.html',
+ styleUrl: './landing-page.component.scss'
+})
+export class LandingPageComponent {
+
+}
diff --git a/frontend/indiestream/src/app/guards/auth.guard.ts b/frontend/indiestream/src/app/guards/auth.guard.ts
new file mode 100644
index 0000000..79bc747
--- /dev/null
+++ b/frontend/indiestream/src/app/guards/auth.guard.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core';
+import {
+ ActivatedRouteSnapshot,
+ CanActivate,
+ RouterStateSnapshot,
+} from '@angular/router';
+import {AuthService} from "../services/auth.service";
+
+@Injectable()
+export class AuthGuard implements CanActivate {
+ constructor(private authService: AuthService) {}
+
+ canActivate(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot,
+ ): boolean {
+ if (!this.authService.isAuthenticated()) {
+ this.authService.login();
+ }
+ return this.authService.isAuthenticated();
+ }
+}
diff --git a/frontend/indiestream/src/app/modules/games.ts b/frontend/indiestream/src/app/modules/games.ts
index c44e0ab..d10d2af 100644
--- a/frontend/indiestream/src/app/modules/games.ts
+++ b/frontend/indiestream/src/app/modules/games.ts
@@ -1,9 +1,3 @@
-export interface Games {
- Message: string,
- Status: string,
- Games: Game[];
-}
-
export interface Game {
id: string,
title: string,
diff --git a/frontend/indiestream/src/app/services/auth.initializer.ts b/frontend/indiestream/src/app/services/auth.initializer.ts
index b14f683..1d4c9d3 100644
--- a/frontend/indiestream/src/app/services/auth.initializer.ts
+++ b/frontend/indiestream/src/app/services/auth.initializer.ts
@@ -28,7 +28,7 @@ export class AuthInitializer {
};
this.oauthService.configure(authConfig);
this.oauthService.setupAutomaticSilentRefresh();
- this.oauthService.loadDiscoveryDocumentAndLogin().then(() => {
+ this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => {
/*
if (this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken()) {
const url = decodeURIComponent(this.oauthService.state);
diff --git a/frontend/indiestream/src/app/services/auth.service.ts b/frontend/indiestream/src/app/services/auth.service.ts
index 2737eed..11ba02c 100644
--- a/frontend/indiestream/src/app/services/auth.service.ts
+++ b/frontend/indiestream/src/app/services/auth.service.ts
@@ -20,6 +20,9 @@ export class AuthService {
getPictureUrl(): string {
const claims = this.oAuthService.getIdentityClaims();
+ if (claims === null) {
+ return '';
+ }
return claims['picture'];
}
@@ -39,6 +42,6 @@ export class AuthService {
}
logout() {
- this.oAuthService.logOut();
+ this.oAuthService.revokeTokenAndLogout();
}
}
diff --git a/frontend/indiestream/src/app/services/games.service.ts b/frontend/indiestream/src/app/services/games.service.ts
index d343090..4938b12 100644
--- a/frontend/indiestream/src/app/services/games.service.ts
+++ b/frontend/indiestream/src/app/services/games.service.ts
@@ -1,48 +1,49 @@
-import { Injectable } from "@angular/core";
-import { HttpClient, HttpEvent, HttpHeaders} from '@angular/common/http';
-import { Games, Game } from '../modules/games';
-import { Observable } from "rxjs";
-import { AppConfigService } from "./app-config.service";
-import { AuthService} from "./auth.service";
-import {FormGroup} from "@angular/forms";
-
-@Injectable({
- providedIn: 'root'
-})
-export class GamesService {
- private apiUrl = this.configService.getConfig().apiUrl;
- constructor(private http: HttpClient, private configService: AppConfigService) {
- console.log(this.configService.getConfig())
- }
-
- getGames(): Observable {
- const httpOptions = {
- headers: new HttpHeaders({
- 'Content-Type': 'application/json',
- }),
- };
- return this.http.get(this.apiUrl + "/games");
- }
-
- getGame(id: string): Observable {
- return this.http.get(this.apiUrl + "/games/" + id)
- }
-
- deleteGame(id: string): void {
- this.http.delete(this.apiUrl + "/games/" + id)
- }
-
- uploadGame(gameForm: FormGroup): Observable> {
- const formData = new FormData();
- formData.append('title', gameForm.get('title')?.value);
- const files: FileList = gameForm.get('file')?.value;
- if (files && files.length > 0) {
- formData.append('file', files[0]);
- }
- return this.http.post(this.apiUrl + "/games", formData, {
- reportProgress: true,
- observe: 'events'
- });
- }
-}
-
+import { Injectable } from "@angular/core";
+import { HttpClient, HttpEvent, HttpHeaders} from '@angular/common/http';
+import { Game } from '../modules/games';
+import {map, Observable, tap} from "rxjs";
+import { AppConfigService } from "./app-config.service";
+import { AuthService} from "./auth.service";
+import {FormGroup} from "@angular/forms";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class GamesService {
+ private apiUrl = this.configService.getConfig().apiUrl;
+ constructor(private http: HttpClient, private configService: AppConfigService) {
+ console.log(this.configService.getConfig())
+ }
+
+ getGames(): Observable {
+ const httpOptions = {
+ headers: new HttpHeaders({
+ 'Content-Type': 'application/json',
+ }),
+ };
+ return this.http.get(this.apiUrl + "/games");
+ }
+
+ // returns undefined when game not found
+ getGame(id: string): Observable {
+ return this.http.get(this.apiUrl + "/games/" + id);
+ }
+
+ deleteGame(id: string): void {
+ this.http.delete(this.apiUrl + "/games/" + id)
+ }
+
+ uploadGame(gameForm: FormGroup): Observable> {
+ const formData = new FormData();
+ formData.append('title', gameForm.get('title')?.value);
+ const files: FileList = gameForm.get('file')?.value;
+ if (files && files.length > 0) {
+ formData.append('file', files[0]);
+ }
+ return this.http.post(this.apiUrl + "/games", formData, {
+ reportProgress: true,
+ observe: 'events'
+ });
+ }
+}
+
diff --git a/frontend/indiestream/src/index.html b/frontend/indiestream/src/index.html
index fb3dde5..f6a4754 100644
--- a/frontend/indiestream/src/index.html
+++ b/frontend/indiestream/src/index.html
@@ -2,7 +2,7 @@
- Indiestream
+ IndieGameStream
diff --git a/frontend/indiestream/src/styles.scss b/frontend/indiestream/src/styles.scss
index ac0b6ef..0a4f1db 100644
--- a/frontend/indiestream/src/styles.scss
+++ b/frontend/indiestream/src/styles.scss
@@ -1,4 +1,11 @@
/* You can add global styles to this file, and also import other style files */
-/*html, body { height: 100%; }
-body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }*/
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+
+.text-with-icon {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding:0.25rem;
+}