Skip to content

Commit

Permalink
Claim Polls & Advertise Accounts (#181)
Browse files Browse the repository at this point in the history
* feat(frontend): Add Claim Polls in dashboard

* feat(backend): Claim polls

* feat(frontend): Ad for accounts

* chore: Fix lint problems

* feat(backend): Claim participations

* fix(frontend): Consistent terms

---------

Co-authored-by: Clashsoft <Clashsoft@users.noreply.github.com>
  • Loading branch information
Clashsoft and Clashsoft authored Jul 27, 2024
1 parent c23f8e7 commit e09bee5
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 5 deletions.
11 changes: 10 additions & 1 deletion apps/backend/src/poll/poll/poll.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ReadStatsPollDto,
UpdateParticipantDto,
} from '@apollusia/types';
import {AuthUser, UserToken} from '@mean-stream/nestx/auth';
import {Auth, AuthUser, UserToken} from '@mean-stream/nestx/auth';
import {ObjectIdPipe} from '@mean-stream/nestx/ref';
import {
Body,
Expand Down Expand Up @@ -52,6 +52,15 @@ export class PollController {
return this.pollService.getPolls(token, active !== undefined ? active === 'true' : undefined);
}

@Post('claim/:token')
@Auth()
async claimPolls(
@AuthUser() user: UserToken,
@Param('token') token: string,
): Promise<void> {
return this.pollService.claimPolls(token, user.sub);
}

@Get(':id/admin/:token')
async isAdmin(@Param('id', ObjectIdPipe) id: Types.ObjectId, @Param('token') token: string): Promise<boolean> {
return this.pollService.isAdmin(id, token);
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/src/poll/poll/poll.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,9 @@ export class PollService implements OnModuleInit {
async isAdmin(id: Types.ObjectId, token: string) {
return this.pollModel.findById(id).exec().then(poll => poll.adminToken === token);
}

async claimPolls(adminToken: string, createdBy: string): Promise<void> {
await this.pollModel.updateMany({adminToken}, {createdBy}).exec();
await this.participantModel.updateMany({token: adminToken}, {createdBy}, {timestamps: false}).exec();
}
}
6 changes: 3 additions & 3 deletions apps/frontend/src/app/core/navbar/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div ngbDropdownMenu class="dropdown-menu dropdown-menu-end">
@if (user) {
<h5 class="dropdown-header">
Logged in as <strong>{{ user.username }}</strong>
Signed in as <strong>{{ user.username }}</strong>
</h5>
<a ngbDropdownItem href="{{ environment.keycloak.url }}/realms/{{ environment.keycloak.realm }}/account" target="_blank" class="bi-person-gear">
Account Settings
Expand All @@ -22,11 +22,11 @@ <h5 class="dropdown-header">
Change Password & 2FA
</a>
<button ngbDropdownItem (click)="logout()" class="text-warning bi-box-arrow-right">
Logout
Sign Out
</button>
} @else {
<button ngbDropdownItem (click)="login()" class="bi-box-arrow-in-right">
Login
Sign In
</button>
}
<div class="dropdown-divider"></div>
Expand Down
49 changes: 49 additions & 0 deletions apps/frontend/src/app/dashboard/dashboard/dashboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,54 @@ <h1 class="mb-4">
</li>
</ul>
</div>
<div class="col-md-3">
@if (!loggedIn) {
<div class="card">
<div class="card-body">
<img class="card-img" src="/assets/artwork/account.svg" alt="Account" width="100%">
<h5 class="card-title">
Sign In to Apollusia
<span class="badge bg-primary">New</span>
</h5>
<p class="card-text">
You can now log into Apollusia with an account, so you can manage your polls on all your devices and continue to use these features:
</p>
<ul class="card-text">
<li>Email notifications for your polls</li>
<li>Push notifications for your polls</li>
<li>Email notifications for your participations</li>
</ul>
<p class="card-text">
We will also add the following features built on accounts soon:
</p>
<ul>
<li>Additional poll admins</li>
<li>Push notifications for your participations</li>
</ul>
<button class="btn btn-primary w-100 mb-3" (click)="login()">
Sign Up
</button>
<p class="card-text small text-muted">
<i class="bi-info-circle"></i>
Accounts are free, secure and forever optional.
<a href="https://github.com/Morphclue/apollusia/issues/169" target="_blank">Learn more</a>
</p>
</div>
</div>
}
@if (loggedIn && unclaimed && !participated) {
<div class="alert alert-warning">
<p>
It looks like some of your polls are not associated with your account yet.
</p>
<p>
To ensure you can always manage them on all your devices, you can claim them now.
</p>
<button class="btn btn-primary" (click)="claim()">
Claim Polls
</button>
</div>
}
</div>
</div>
</div>
29 changes: 28 additions & 1 deletion apps/frontend/src/app/dashboard/dashboard/dashboard.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {ToastService} from '@mean-stream/ngbx';
import {KeycloakService} from 'keycloak-angular';
import {switchMap, tap} from 'rxjs/operators';

import {TokenService} from '../../core/services';
import {ReadPoll} from '../../model';
import {PollService} from '../../poll/services/poll.service';


@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
Expand All @@ -13,15 +17,24 @@ import {PollService} from '../../poll/services/poll.service';
export class DashboardComponent implements OnInit {
polls: ReadPoll[] = [];
participated = false;
loggedIn = false;
unclaimed = false;
adminToken = '';
searchText = '';

constructor(
private pollService: PollService,
private route: ActivatedRoute,
private toastService: ToastService,
private keycloakService: KeycloakService,
tokenService: TokenService,
) {
this.loggedIn = keycloakService.isLoggedIn();
this.adminToken = tokenService.getToken();
}

ngOnInit(): void {

this.route.queryParams.pipe(
tap(({participated}) => this.participated = participated),
switchMap(({participated, active}) => {
Expand All @@ -30,6 +43,20 @@ export class DashboardComponent implements OnInit {
}
return this.pollService.getOwn(active !== 'false');
}),
).subscribe(polls => this.polls = polls);
).subscribe(polls => {
this.polls = polls;
this.unclaimed = polls.some(poll => !poll.createdBy);
});
}

claim() {
this.pollService.claim(this.adminToken).subscribe(() => {
this.toastService.success('Claim Polls', 'Successfully claimed polls.');
this.unclaimed = false;
});
}

login() {
this.keycloakService.login();
}
}
4 changes: 4 additions & 0 deletions apps/frontend/src/app/poll/services/poll.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class PollService {
return Date.parse(event.start) < Date.now();
}

claim(adminToken: string) {
return this.http.post(`${environment.backendURL}/poll/claim/${adminToken}`, {});
}

getOwn(active?: boolean): Observable<ReadPoll[]> {
return this.http.get<ReadPoll[]>(`${environment.backendURL}/poll`, {
params: active !== undefined ? {
Expand Down
168 changes: 168 additions & 0 deletions apps/frontend/src/assets/artwork/account.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e09bee5

Please sign in to comment.