Skip to content

Commit

Permalink
Add User Menu + Leaderboard highlight (#126)
Browse files Browse the repository at this point in the history
Co-authored-by: Felix T.J. Dietrich <[email protected]>
  • Loading branch information
GODrums and FelixTJDietrich authored Oct 27, 2024
1 parent c1ed5d0 commit 6b59dc6
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IssueCommentRepository extends JpaRepository<IssueComment, Long
LEFT JOIN FETCH ic.issue
LEFT JOIN FETCH ic.issue.repository
WHERE
ic.author.login = :authorLogin AND ic.createdAt >= :activitySince
ic.author.login ILIKE :authorLogin AND ic.createdAt >= :activitySince
AND (:onlyFromPullRequests = false OR ic.issue.htmlUrl LIKE '%/pull/%')
ORDER BY ic.createdAt DESC
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface PullRequestRepository extends JpaRepository<PullRequest, Long>
@Query("""
SELECT MIN(p.createdAt)
FROM PullRequest p
WHERE p.author.login = :authorLogin
WHERE p.author.login ILIKE :authorLogin
""")
Optional<OffsetDateTime> firstContributionByAuthorLogin(@Param("authorLogin") String authorLogin);

Expand All @@ -27,7 +27,7 @@ SELECT MIN(p.createdAt)
JOIN FETCH p.author
LEFT JOIN FETCH p.assignees
LEFT JOIN FETCH p.repository
WHERE (p.author.login = :assigneeLogin OR :assigneeLogin IN (SELECT u.login FROM p.assignees u)) AND p.state IN :states
WHERE (p.author.login ILIKE :assigneeLogin OR LOWER(:assigneeLogin) IN (SELECT LOWER(u.login) FROM p.assignees u)) AND p.state IN :states
ORDER BY p.createdAt DESC
""")
List<PullRequest> findAssignedByLoginAndStates(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface PullRequestReviewRepository extends JpaRepository<PullRequestRe
LEFT JOIN FETCH prr.pullRequest
LEFT JOIN FETCH prr.pullRequest.repository
LEFT JOIN FETCH prr.comments
WHERE prr.author.login = :authorLogin AND prr.submittedAt >= :activitySince
WHERE prr.author.login ILIKE :authorLogin AND prr.submittedAt >= :activitySince
ORDER BY prr.submittedAt DESC
""")
List<PullRequestReview> findAllByAuthorLoginSince(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface RepositoryRepository
SELECT r
FROM Repository r
JOIN PullRequest pr ON r.id = pr.repository.id
WHERE pr.author.login = :contributorLogin
WHERE pr.author.login ILIKE :contributorLogin
ORDER BY r.name ASC
""")
List<Repository> findContributedByLogin(@Param("contributorLogin") String contributorLogin);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
@Query("""
SELECT u
FROM User u
WHERE u.login = :login
WHERE u.login ILIKE :login
""")
Optional<User> findByLogin(@Param("login") String login);
}
33 changes: 29 additions & 4 deletions webapp/src/app/core/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,37 @@
<lucide-angular [img]="Hammer" class="size-8 sm:size-6" />
<span class="hidden sm:inline-block text-xl font-semibold">Hephaestus</span>
</a>
<app-request-feature class="hidden sm:inline-block" />
<app-request-feature class="sm:hidden" iconOnly />
<app-theme-switcher />
@if (signedIn()) {
<button hlmBtn variant="outline" (click)="signOut()">Sign&nbsp;Out</button>
<button [brnMenuTriggerFor]="usermenu" class="ml-2">
<hlm-avatar>
<img [src]="'https://github.com/' + user()!.username + '.png'" [alt]="user()?.name + '\'s avatar'" hlmAvatarImage />
<span hlmAvatarFallback>
{{ user()?.name?.slice(0, 2)?.toUpperCase() ?? '?' }}
</span>
</hlm-avatar>
</button>

<ng-template #usermenu>
<hlm-menu>
<hlm-menu-label>{{ user()!.name }}</hlm-menu-label>
<hlm-menu-separator />
<hlm-menu-group>
<a hlmMenuItem [routerLink]="'/user/' + user()!.username" class="cursor-pointer">
<hlm-icon name="lucideUser" hlmMenuIcon />
<span>My Profile</span>
</a>
<hlm-menu-separator />
<button hlmMenuItem (click)="signOut()" class="cursor-pointer">
<hlm-icon name="lucideLogOut" hlmMenuIcon />
<span>Sign&nbsp;Out</span>
</button>
</hlm-menu-group>
</hlm-menu>
</ng-template>
} @else {
<button hlmBtn (click)="signIn()">Sign&nbsp;In</button>
}
<app-request-feature class="hidden sm:inline-block" />
<app-request-feature class="sm:hidden" iconOnly />
<app-theme-switcher />
</header>
28 changes: 26 additions & 2 deletions webapp/src/app/core/header/header.component.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
import { Component, inject } from '@angular/core';
import { RouterLink } from '@angular/router';
import { RouterLink, RouterModule } from '@angular/router';
import { LucideAngularModule, Hammer } from 'lucide-angular';
import { HlmButtonModule } from '@spartan-ng/ui-button-helm';
import { HlmAvatarModule } from '@spartan-ng/ui-avatar-helm';
import { HlmMenuModule } from '@spartan-ng/ui-menu-helm';
import { HlmIconComponent } from '@spartan-ng/ui-icon-helm';
import { BrnMenuTriggerDirective } from '@spartan-ng/ui-menu-brain';
import { SecurityStore } from '@app/core/security/security-store.service';
import { ThemeSwitcherComponent } from '@app/core/theme/theme-switcher.component';
import { RequestFeatureComponent } from './request-feature/request-feature.component';
import { environment } from 'environments/environment';
import { lucideUser, lucideLogOut } from '@ng-icons/lucide';
import { provideIcons } from '@ng-icons/core';

@Component({
selector: 'app-header',
templateUrl: './header.component.html',
standalone: true,
imports: [RouterLink, LucideAngularModule, ThemeSwitcherComponent, HlmButtonModule, RequestFeatureComponent]
imports: [
RouterLink,
RouterModule,
LucideAngularModule,
ThemeSwitcherComponent,
HlmButtonModule,
RequestFeatureComponent,
HlmAvatarModule,
HlmMenuModule,
BrnMenuTriggerDirective,
HlmIconComponent
],
providers: [
provideIcons({
lucideUser,
lucideLogOut
})
]
})
export class HeaderComponent {
protected Hammer = Hammer;

securityStore = inject(SecurityStore);
signedIn = this.securityStore.signedIn;
user = this.securityStore.loadedUser;

protected signOut() {
this.securityStore.signOut();
Expand Down
7 changes: 5 additions & 2 deletions webapp/src/app/core/security/keycloak.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { environment } from 'environments/environment';
import Keycloak from 'keycloak-js';

export interface UserProfile {
sub: string;
email: string;
email_verified: boolean;
given_name: string;
family_name: string;
name: string;
preferred_username: string;
realmAccess: { roles: string[] };
token: string;
roles: string[];
sub: string;
token: string;
}

@Injectable({ providedIn: 'root' })
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/app/core/security/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface User {
id: string;
email: string;
name: string;
username: string;
anonymous: boolean;
bearer: string;
roles: string[];
Expand All @@ -11,6 +12,7 @@ export const ANONYMOUS_USER: User = {
id: '',
email: 'nomail',
name: 'no user',
username: 'anon',
anonymous: true,
bearer: '',
roles: []
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/app/core/security/security-store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ export class SecurityStore {

const isLoggedIn = await keycloakService.init();
if (isLoggedIn && keycloakService.profile) {
const { sub, email, given_name, family_name, token, roles } = keycloakService.profile;
const { sub, email, token, roles, name, preferred_username: username } = keycloakService.profile;
const user = {
id: sub,
email,
name: `${given_name} ${family_name}`,
name,
username,
anonymous: false,
bearer: token,
roles
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/app/home/leaderboard/leaderboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</tr>
} @else {
@for (entry of leaderboard(); track entry.user.login) {
<tr appTableRow routerLink="/user/{{ entry.user.login }}" routerLinkActive="active" ariaCurrentWhenActive="page" class="cursor-pointer">
<tr appTableRow routerLink="/user/{{ entry.user.login }}" routerLinkActive="active" ariaCurrentWhenActive="page" [class]="trClass(entry)">
<td appTableCell class="text-center">{{ entry.rank }}</td>
<td appTableCell class="py-2">
<span class="flex items-center gap-2 font-medium">
Expand Down
12 changes: 11 additions & 1 deletion webapp/src/app/home/leaderboard/leaderboard.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, input } from '@angular/core';
import { Component, inject, input } from '@angular/core';
import { NgIconComponent } from '@ng-icons/core';
import { octFileDiff, octCheck, octComment, octCommentDiscussion, octGitPullRequest, octChevronLeft, octNoEntry } from '@ng-icons/octicons';
import { LeaderboardEntry } from 'app/core/modules/openapi';
Expand All @@ -13,6 +13,8 @@ import { TableComponent } from 'app/ui/table/table.component';
import { HlmAvatarModule } from '@spartan-ng/ui-avatar-helm';
import { HlmSkeletonModule } from '@spartan-ng/ui-skeleton-helm';
import { RouterLink, RouterOutlet } from '@angular/router';
import { SecurityStore } from '@app/core/security/security-store.service';
import { cn } from '@app/utils';

@Component({
selector: 'app-leaderboard',
Expand All @@ -35,6 +37,7 @@ import { RouterLink, RouterOutlet } from '@angular/router';
templateUrl: './leaderboard.component.html'
})
export class LeaderboardComponent {
securityStore = inject(SecurityStore);
protected octFileDiff = octFileDiff;
protected octCheck = octCheck;
protected octComment = octComment;
Expand All @@ -46,6 +49,13 @@ export class LeaderboardComponent {
protected Math = Math;
protected Array = Array;

signedIn = this.securityStore.signedIn;
user = this.securityStore.loadedUser;

trClass = (entry: LeaderboardEntry) => {
return cn('cursor-pointer', this.signedIn() && this.user()?.username.toLowerCase() === entry.user.login.toLowerCase() ? 'bg-accent' : '');
};

leaderboard = input<LeaderboardEntry[]>();
isLoading = input<boolean>();
}
4 changes: 3 additions & 1 deletion webapp/src/app/user/user-profile.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BrnTooltipContentDirective } from '@spartan-ng/ui-tooltip-brain';
import { HlmTooltipComponent, HlmTooltipTriggerDirective } from '@spartan-ng/ui-tooltip-helm';
import { HlmButtonModule } from '@spartan-ng/ui-button-helm';
import { HlmScrollAreaComponent } from '@spartan-ng/ui-scrollarea-helm';
import { HlmAlertModule } from '@spartan-ng/ui-alert-helm';
import { ReviewActivityCardComponent } from '@app/user/review-activity-card/review-activity-card.component';
import { IssueCardComponent } from '@app/user/issue-card/issue-card.component';
import { combineLatest, lastValueFrom, map, timer } from 'rxjs';
Expand All @@ -32,7 +33,8 @@ import { UserHeaderComponent } from './header/header.component';
BrnTooltipContentDirective,
HlmButtonModule,
HlmScrollAreaComponent,
UserHeaderComponent
UserHeaderComponent,
HlmAlertModule
],
templateUrl: './user-profile.component.html'
})
Expand Down

0 comments on commit 6b59dc6

Please sign in to comment.