Skip to content

Commit

Permalink
Merge pull request #77 from turecross321/contests
Browse files Browse the repository at this point in the history
Contests
  • Loading branch information
jvyden authored May 6, 2024
2 parents 3c8addb + cab6935 commit 0d8d708
Show file tree
Hide file tree
Showing 32 changed files with 1,582 additions and 92 deletions.
740 changes: 740 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dayjs": "^1.11.7",
"express": "^4.15.2",
"masonry-layout": "github:cliqqz/masonry#ffd2b614237176fd184b70b90a4559f9716dd29c",
"ngx-markdown": "^16.0.0",
"ngx-masonry": "^14.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
Expand All @@ -56,4 +57,4 @@
"tw-colors": "^3.2.0",
"typescript": "~4.9.4"
}
}
}
42 changes: 37 additions & 5 deletions src/app/api/api-client.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import {Instance} from "./types/instance";
import {ApiRequestCreator} from "./api-request.creator";
import {LevelEditRequest} from "./types/level-edit-request";
import {Asset} from "./types/asset";
import {ExtendedUser} from "./types/extended-user";
import {Contest} from "./types/contests/contest";
import {ContestEditRequest} from "./types/contests/contest-edit-request";
import {Params} from "@angular/router";

@Injectable({providedIn: 'root'})
export class ApiClient {
Expand Down Expand Up @@ -83,8 +85,15 @@ export class ApiClient {
return this.makeRequest<Category[]>("GET", "levels?includePreviews=true");
}

public GetLevelListing(route: string, count: number = 20, skip: number = 0): Observable<ApiListResponse<Level>> {
return this.makeListRequest<Level>("GET", `levels/${route}?count=${count}&skip=${skip}`);
public GetLevelListing(route: string, count: number = 20, skip: number = 0, params: Params = {}): Observable<ApiListResponse<Level>> {
let query: string = `count=${count}&skip=${skip}`;

for (let param in params) {
query += `&${param}=${params[param]}`
}


return this.makeListRequest<Level>("GET", `levels/${route}?${query}`);
}

public GetLevelById(id: number): Observable<Level> {
Expand Down Expand Up @@ -146,7 +155,7 @@ export class ApiClient {

public EditLevel(level: LevelEditRequest, id: number, admin: boolean = false): void {
let endpoint: string = "levels/id/" + id;
if(admin) endpoint = "admin/" + endpoint;
if (admin) endpoint = "admin/" + endpoint;
this.apiRequestCreator.makeRequest("PATCH", endpoint, level)
.subscribe(_ => {
this.bannerService.pushSuccess("Level Updated", `${level.title} was successfully updated.`);
Expand All @@ -155,7 +164,7 @@ export class ApiClient {

public UpdateLevelIcon(hash: string, id: number, admin: boolean = false): void {
let endpoint: string = "levels/id/" + id;
if(admin) endpoint = "admin/" + endpoint;
if (admin) endpoint = "admin/" + endpoint;
this.apiRequestCreator.makeRequest("PATCH", endpoint, {iconHash: hash})
.subscribe(_ => {
this.bannerService.pushSuccess("Icon updated", "The level's icon was successfully updated.");
Expand All @@ -179,6 +188,29 @@ export class ApiClient {
public UploadImageAsset(hash: string, data: ArrayBuffer): Observable<Asset> {
return this.apiRequestCreator.makeRequest("POST", `assets/${hash}`, data);
}

public GetContests(): Observable<Contest[]> {
return this.makeRequest<Contest[]>("GET", "contests");
}

public GetContestById(contestId: string): Observable<Contest> {
return this.makeRequest<Contest>("GET", "contests/" + contestId);
}

public CreateContest(contest: ContestEditRequest): Observable<Contest> {
return this.makeRequest<Contest>("POST", "admin/contests/" + contest.contestId, contest);
}

public UpdateContest(contest: ContestEditRequest): Observable<Contest> {
return this.makeRequest<Contest>("PATCH", "contests/" + contest.contestId, contest);
}

public DeleteContest(contest: Contest): void {
this.apiRequestCreator.makeRequest("DELETE", "admin/contests/" + contest.contestId)
.subscribe(_ => {
this.bannerService.pushWarning("Contest Deleted", `${contest.contestTitle} was successfully removed.`);
});
}
}

export function GetPhotoLink(photo: Photo, large: boolean = true): string {
Expand Down
21 changes: 21 additions & 0 deletions src/app/api/types/contests/contest-edit-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {GameVersion} from "../game-version";

export interface ContestEditRequest {
contestId: string | undefined;
organizerId: string | undefined;

startDate: Date | undefined;
endDate: Date | undefined;

contestTag: string | undefined;
bannerUrl: string | undefined;

contestTitle: string | undefined;
contestSummary: string | undefined;
contestDetails: string | undefined;
contestTheme: string | undefined;

templateLevelId: number | undefined;

allowedGames: GameVersion[] | undefined;
}
23 changes: 23 additions & 0 deletions src/app/api/types/contests/contest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {User} from "../user";
import {GameVersion} from "../game-version";
import {Level} from "../level";

export interface Contest {
contestId: string;
organizer: User;

creationDate: Date;
startDate: Date;
endDate: Date;

contestTag: string;
bannerUrl: string;

contestTitle: string;
contestSummary: string;
contestDetails: string;
contestTheme: string;

templateLevel: Level
allowedGames: GameVersion[];
}
2 changes: 2 additions & 0 deletions src/app/api/types/instance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Announcement} from "./announcement";
import {ContactInfo} from "./contactInfo";
import {Contest} from "./contests/contest";

export interface Instance {
instanceName: string;
Expand All @@ -17,4 +18,5 @@ export interface Instance {
grafanaDashboardUrl: string | null;

contactInfo: ContactInfo;
activeContest: Contest | null;
}
14 changes: 9 additions & 5 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {adminAuthenticationGuard} from "./api/guards/admin-authentication.guard"
import {noAuthenticationGuard} from "./api/guards/no-authentication.guard";
import {ForgotPasswordComponent} from "./pages/forgot-password/forgot-password.component";
import {StatisticsComponent} from "./pages/statistics/statistics.component";
import {ContestsComponent} from "./pages/contests/contests.component";
import {ManageContestComponent} from "./pages/manage-contest/manage-contest.component";
import {ContestComponent} from "./pages/contest/contest.component";

const routes: Routes = [
{path: "", component: MainComponent},
Expand Down Expand Up @@ -76,11 +79,12 @@ const routes: Routes = [
{path: "admin/user/:uuid", component: AdminUserComponent, canActivate: [adminAuthenticationGuard]},
{path: "admin/users", component: AdminUsersComponent, canActivate: [adminAuthenticationGuard]},
{path: "admin/registrations", redirectTo: "admin/queuedRegistrations"},
{
path: "admin/queuedRegistrations",
component: AdminRegistrationsComponent,
canActivate: [adminAuthenticationGuard]
},
{path: "admin/queuedRegistrations", component: AdminRegistrationsComponent, canActivate: [adminAuthenticationGuard]},
{path: "admin/newContest", component: ManageContestComponent, canActivate: [adminAuthenticationGuard]},

{path: "contests", component: ContestsComponent},
{path: "contests/:id", component: ContestComponent},
{path: "contests/:id/manage", component: ManageContestComponent},
];

if (isDevMode()) {
Expand Down
18 changes: 10 additions & 8 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
faBell,
faBookBookmark,
faCameraAlt,
faCertificate, faEnvelope,
faCertificate,
faEnvelope,
faExclamationTriangle,
faFireAlt,
faGear,
faMedal,
faSignIn,
faSquarePollVertical,
faWrench
Expand Down Expand Up @@ -79,6 +81,11 @@ export class AppComponent {
];

rightSideRouterLinks: HeaderLink[] = []
protected readonly GetAssetImageLink = GetAssetImageLink;
protected readonly faSignIn = faSignIn;
protected readonly faExclamationTriangle = faExclamationTriangle;
protected readonly UserRoles = UserRoles;
protected readonly faBell = faBell;

constructor(authService: AuthService, apiClient: ApiClient, public bannerService: BannerService, embedService: EmbedService, titleService: TitleService, public themeService: ThemeService) {
authService.userWatcher.subscribe((data) => this.handleUserUpdate(data))
Expand All @@ -101,7 +108,7 @@ export class AppComponent {
}
}

getTheme(): string{
getTheme(): string {
return this.themeService.GetTheme();
}

Expand All @@ -118,6 +125,7 @@ export class AppComponent {

this.rightSideRouterLinks.push(new HeaderLink("API Documentation", "/documentation", faBookBookmark))
this.rightSideRouterLinks.push(new HeaderLink("Server Statistics", "/statistics", faSquarePollVertical))
this.rightSideRouterLinks.push(new HeaderLink("Contests", "/contests", faMedal))
this.rightSideRouterLinks.push(new HeaderLink("Notifications", "/notifications", faBell))
this.rightSideRouterLinks.push(new HeaderLink("Settings", "/settings", faGear))
this.rightSideRouterLinks.push(new HeaderLink("Contact Us", `mailto:${this.instance?.contactInfo.emailAddress}`, faEnvelope, true))
Expand All @@ -137,10 +145,4 @@ export class AppComponent {
this.menu.nativeElement.hidden = true;
this.notifications.nativeElement.hidden = !this.notifications.nativeElement.hidden;
}

protected readonly GetAssetImageLink = GetAssetImageLink;
protected readonly faSignIn = faSignIn;
protected readonly faExclamationTriangle = faExclamationTriangle;
protected readonly UserRoles = UserRoles;
protected readonly faBell = faBell;
}
25 changes: 23 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
faCalendar,
faCertificate,
faCheckCircle,
faCircleExclamation, faFire, faFireAlt,
faCircleExclamation,
faFire,
faForward,
faHeart,
faKey,
Expand Down Expand Up @@ -95,7 +96,15 @@ import {FormsModule} from "@angular/forms";
import {ForgotPasswordComponent} from './pages/forgot-password/forgot-password.component';
import {DateComponent} from './components/date/date.component';
import {StatisticsComponent} from './pages/statistics/statistics.component';
import { ContainerComponent } from './components/container/container.component';
import {ContainerComponent} from './components/container/container.component';
import {ContestsComponent} from './pages/contests/contests.component';
import {ManageContestComponent} from './pages/manage-contest/manage-contest.component';
import {ContestComponent} from './pages/contest/contest.component';
import {ContestBannerComponent} from './components/contest-banner/contest-banner.component';
import {MarkdownModule, MarkedOptions} from "ngx-markdown";
import {markedOptionsFactory} from "./markdown.config";
import {ContestPreviewComponent} from "./components/contest-preview/contest-preview.component";
import {ContestLabelComponent} from "./components/contest-label/contest-label.component";

@NgModule({
declarations: [
Expand Down Expand Up @@ -162,6 +171,12 @@ import { ContainerComponent } from './components/container/container.component';
DateComponent,
StatisticsComponent,
ContainerComponent,
ContestsComponent,
ManageContestComponent,
ContestComponent,
ContestBannerComponent,
ContestPreviewComponent,
ContestLabelComponent,
],
imports: [
BrowserModule,
Expand All @@ -173,6 +188,12 @@ import { ContainerComponent } from './components/container/container.component';
NgxMasonryModule,
NgOptimizedImage,
FormsModule,
MarkdownModule.forRoot({
markedOptions: {
provide: MarkedOptions,
useFactory: markedOptionsFactory,
},
})
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: ApiTokenInterceptor, multi: true},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
<page-header class="text-2xl">
<fa-icon [icon]="Icon" *ngIf="Icon" class="pr-1.5"></fa-icon>
<a [routerLink]="'/levels/' + Route" class="cursor-pointer pr-1">{{Title}}</a>
<span-gentle>({{Total}} total)</span-gentle>
<fa-icon *ngIf="icon" [icon]="icon" class="pr-1.5"></fa-icon>
<a [queryParams]="params" [routerLink]="'/levels/' + route" class="cursor-pointer pr-1">{{ title }}</a>
<span-gentle>({{ total }} total)</span-gentle>
</page-header>

<carousel>
<level-preview *ngFor="let level of Levels" [level]="level"></level-preview>
</carousel>
<ng-container [ngSwitch]="type">
<carousel *ngSwitchCase="CategoryPreviewType.Carousel">
<level-preview *ngFor="let level of levels" [level]="level"></level-preview>
</carousel>

<div *ngSwitchCase="CategoryPreviewType.List" class="flex flex-col gap-2">
<container *ngFor="let level of levels">
<level-preview [level]="level"></level-preview>
</container>
<secondary-button *ngIf="levels && total > levels.length" [queryParams]="params"
[routerLink]="'/levels/' + route"
text="More"></secondary-button>
</div>
</ng-container>
20 changes: 15 additions & 5 deletions src/app/components/category-preview/category-preview.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import {Component, Input} from '@angular/core';
import {Level} from "../../api/types/level";
import {IconProp} from "@fortawesome/fontawesome-svg-core";
import {Params} from "@angular/router";

@Component({
selector: 'category-preview',
templateUrl: './category-preview.component.html'
})
export class CategoryPreviewComponent {
@Input("icon") public Icon: IconProp | undefined;
@Input("category-title") public Title: string = "";
@Input("route") public Route: string = "";
@Input("levels") public Levels: Level[] | undefined;
@Input("total") public Total: number = 0;
@Input("icon") public icon: IconProp | undefined;
@Input("category-title") public title: string = "";
@Input("route") public route: string = "";
@Input("levels") public levels: Level[] | undefined;
@Input("total") public total: number = 0;
@Input("query") public params: Params = {};
@Input("type") public type: CategoryPreviewType = CategoryPreviewType.Carousel;

protected readonly CategoryPreviewType = CategoryPreviewType;
}

export enum CategoryPreviewType {
Carousel,
List
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<img [src]="contest.bannerUrl" class="w-full rounded-md">
10 changes: 10 additions & 0 deletions src/app/components/contest-banner/contest-banner.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {Component, Input} from '@angular/core';
import {Contest} from "../../api/types/contests/contest";

@Component({
selector: 'app-contest-banner',
templateUrl: './contest-banner.component.html'
})
export class ContestBannerComponent {
@Input({required: true}) contest: Contest = null!;
}
4 changes: 4 additions & 0 deletions src/app/components/contest-label/contest-label.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p class="text-2xl font-bold">{{ title }}</p>
<div class="h-full w-full items-center flex justify-center">
<p class="text-xl py-5 px-8 text-center">{{ text }}</p>
</div>
10 changes: 10 additions & 0 deletions src/app/components/contest-label/contest-label.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {Component, Input} from '@angular/core';

@Component({
selector: 'app-contest-label',
templateUrl: './contest-label.component.html',
})
export class ContestLabelComponent {
@Input() title: string = "Title";
@Input() text: string = "Text";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<container *ngIf="_contest">
<div class="flex items-center gap-x-2 bg p-4">
<app-contest-banner [contest]="_contest"></app-contest-banner>
</div>
</container>
15 changes: 15 additions & 0 deletions src/app/components/contest-preview/contest-preview.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Component, Input} from '@angular/core';
import {Level} from "../../api/types/level";
import {Contest} from "../../api/types/contests/contest";

@Component({
selector: 'app-contest-preview',
templateUrl: './contest-preview.component.html',
})
export class ContestPreviewComponent {
_contest: Contest | undefined = undefined;
@Input()
set contest(contest: Contest | undefined) {
this._contest = contest;
}
}
Loading

0 comments on commit 0d8d708

Please sign in to comment.