feat(web): English paths, and a portfolio that updates itself
/cartera, /explorar and /p/:key/proponer are /portfolio, /explore and /p/:key/propose now, like every other path; the Spanish ones redirect, so links already shared keep working. Settings sections go by ?section= with English names. The portfolio reads itself again, quietly, when a ticket, sprint or release of the projects shown changes, when my projects change, or when the live connection comes back; «En directo» replaces the refresh button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,3 +16,11 @@ export const portfolioApiEvents = eventGroup({
|
||||
loadFailed: type<string>(),
|
||||
},
|
||||
});
|
||||
|
||||
/** Something changed in one of my projects: reload, quietly. */
|
||||
export const portfolioLiveEvents = eventGroup({
|
||||
source: 'Portfolio Live',
|
||||
events: {
|
||||
changed: type<void>(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,10 +10,18 @@ import {
|
||||
import {
|
||||
PortfolioProject,
|
||||
ProjectsGateway,
|
||||
TrackerRealtimeGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
import { switchMap } from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
merge,
|
||||
switchMap,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
portfolioApiEvents,
|
||||
portfolioLiveEvents,
|
||||
portfolioPageEvents,
|
||||
} from '../events/portfolio.events';
|
||||
import { describeError } from '../notifications';
|
||||
@@ -25,7 +33,7 @@ interface PortfolioState {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/** All my active projects at a glance. Scoped to the portfolio page. */
|
||||
/** All my active projects at a glance, live. Scoped to the portfolio page. */
|
||||
export const PortfolioStore = signalStore(
|
||||
withState<PortfolioState>({
|
||||
projects: [],
|
||||
@@ -47,17 +55,46 @@ export const PortfolioStore = signalStore(
|
||||
})),
|
||||
),
|
||||
withEventHandlers(
|
||||
(_store, events = inject(Events), projects = inject(ProjectsGateway)) => ({
|
||||
load$: events.on(portfolioPageEvents.opened).pipe(
|
||||
switchMap(() =>
|
||||
projects.portfolio().pipe(
|
||||
mapResponse({
|
||||
next: (list) => portfolioApiEvents.loaded(list),
|
||||
error: (error) =>
|
||||
portfolioApiEvents.loadFailed(describeError(error)),
|
||||
}),
|
||||
(
|
||||
_store,
|
||||
events = inject(Events),
|
||||
projects = inject(ProjectsGateway),
|
||||
realtime = inject(TrackerRealtimeGateway),
|
||||
) => ({
|
||||
// Opened, or something changed (then quietly: no «loading»).
|
||||
load$: events
|
||||
.on(portfolioPageEvents.opened, portfolioLiveEvents.changed)
|
||||
.pipe(
|
||||
switchMap(() =>
|
||||
projects.portfolio().pipe(
|
||||
mapResponse({
|
||||
next: (list) => portfolioApiEvents.loaded(list),
|
||||
error: (error) =>
|
||||
portfolioApiEvents.loadFailed(describeError(error)),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Any ticket, sprint or release of the projects shown, the list of my
|
||||
// projects itself, or a cut connection: read it again, calmly.
|
||||
live$: events.on(portfolioApiEvents.loaded).pipe(
|
||||
map(({ payload }) => payload.map((p) => p.projectId).join(',')),
|
||||
distinctUntilChanged(),
|
||||
switchMap((ids) =>
|
||||
merge(
|
||||
...ids
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
.flatMap((id) => [
|
||||
realtime.issueChanges(id),
|
||||
realtime.sprintChanges(id),
|
||||
realtime.releaseChanges(id),
|
||||
]),
|
||||
realtime.myProjectsChanges(),
|
||||
realtime.reconnects(),
|
||||
).pipe(debounceTime(1500)),
|
||||
),
|
||||
map(() => portfolioLiveEvents.changed()),
|
||||
),
|
||||
}),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user