feat: Challenges Page and Challenge Solve Modal 1.04

This commit is contained in:
OpenVelo Agent
2026-07-23 03:47:08 +00:00
parent 4fd5c37d27
commit 3416ef27c9
7 changed files with 316 additions and 45 deletions
@@ -135,7 +135,7 @@ export class ChallengesPage implements OnInit, OnDestroy {
ngOnInit(): void {
const user = this.auth.currentUser();
if (user?.id) this.store.setMyUserId(user.id);
this.store.setMyUserId(user?.id ?? null);
void this.store.load();
// REST snapshot first so the page has synchronous state even if the SSE
@@ -72,8 +72,25 @@ export class ChallengesStore {
private sse: EventSourceLike | null = null;
private countdownZeroHandler: (() => void) | null = null;
setMyUserId(id: string | null): void {
setMyUserId(id: string | null): string | null {
const prev = this._myUserId();
const hasCachedUserData = this._board() !== null || this._selectedDetail() !== null;
if (hasCachedUserData && prev !== id) {
this.reset();
}
this._myUserId.set(id);
return prev;
}
reset(): void {
this.stop();
this._board.set(null);
this._selectedDetail.set(null);
this._myUserId.set(null);
this._error.set(null);
this._loading.set(false);
this.countdownZeroHandler = null;
this.solveListeners.clear();
}
async load(): Promise<void> {
@@ -224,7 +241,7 @@ export class ChallengesStore {
...card,
livePoints: payload.livePoints ?? card.livePoints,
solveCount: payload.solveCount ?? card.solveCount + 1,
solvedByMe: card.solvedByMe || isMine,
solvedByMe: isMine ? true : card.solvedByMe,
};
}),
})),
@@ -246,7 +263,7 @@ export class ChallengesStore {
...detail.challenge,
livePoints,
solveCount,
solvedByMe: detail.challenge.solvedByMe || isMine,
solvedByMe: isMine ? true : detail.challenge.solvedByMe,
},
solvers: mergeSolveEventIntoSolvers(detail.solvers, payload),
});
@@ -266,7 +283,7 @@ export class ChallengesStore {
...card,
livePoints: response.challenge.livePoints,
solveCount: response.challenge.solveCount,
solvedByMe: response.challenge.solvedByMe || card.solvedByMe,
solvedByMe: response.challenge.solvedByMe,
};
}),
})),
@@ -16,6 +16,7 @@ import { AuthService } from '../../core/services/auth.service';
import { EventStatusStore } from '../../core/services/event-status.store';
import { AuthenticatedEventSourceService } from '../../core/services/authenticated-event-source.service';
import { UserStore } from '../../core/services/user.store';
import { ChallengesStore } from '../challenges/challenges.store';
import { ShellHeaderComponent } from '../shell/header/shell-header.component';
import {
ChangePasswordModalComponent,
@@ -91,6 +92,7 @@ export class HomeComponent implements OnInit, OnDestroy {
readonly eventStatus = inject(EventStatusStore);
private readonly authenticatedEventSource = inject(AuthenticatedEventSourceService);
readonly userStore = inject(UserStore);
private readonly challengesStore = inject(ChallengesStore);
private readonly router = inject(Router);
private readonly destroyRef = inject(DestroyRef);
@@ -200,6 +202,7 @@ export class HomeComponent implements OnInit, OnDestroy {
await this.auth.logout();
this.userMenuOpen.set(false);
this.userStore.reset();
this.challengesStore.reset();
await this.router.navigateByUrl('/login');
}
@@ -210,6 +213,7 @@ export class HomeComponent implements OnInit, OnDestroy {
_reason: 'peer-logout' | 'sse-unauthorized',
): Promise<void> {
this.userStore.reset();
this.challengesStore.reset();
this.changePasswordOpen.set(false);
this.userMenuOpen.set(false);
if (this.navigatingToLogin) return;