-
-
Please sign the following payload with your PGP key:
-
{token}
-
-
- );
+ );
+ }
+ return
;
}
diff --git a/client/src/routes/protected.tsx b/client/src/routes/protected.tsx
index 8c29b16..b69ce47 100644
--- a/client/src/routes/protected.tsx
+++ b/client/src/routes/protected.tsx
@@ -9,7 +9,7 @@ export type ProtectedRouteProps = {
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
const { signedIn } = useAuthContext();
- if (!signedIn) return
;
-
- return children;
+ if (signedIn === false) return
;
+ if (signedIn) return children;
+ return <>>; // While it's undefined - we're checking localstorage
}
diff --git a/client/src/routes/timers.tsx b/client/src/routes/timers.tsx
index 92f8bf9..d83bbb1 100644
--- a/client/src/routes/timers.tsx
+++ b/client/src/routes/timers.tsx
@@ -18,7 +18,6 @@ export default function Timers() {
data: timers,
refreshData: refreshTimers,
setData: setTimers,
- query,
setQuery,
socket,
setEndpoint,
@@ -64,24 +63,22 @@ export default function Timers() {
return (
- {timers ? (
- timers
- .map((timer) => ({
- ...timer,
- start: new Date(timer.start),
- }))
- .sort(
- (
- { start: startA }: { start: Date },
- { start: startB }: { start: Date }
- ) => startB.getTime() - startA.getTime()
- )
- .map((timer) => (
-
- ))
- ) : (
- <>>
- )}
+
+ {timers ? (
+ timers
+ .sort(
+ (
+ { start: startA }: { start: string },
+ { start: startB }: { start: string }
+ ) => new Date(startB).getTime() - new Date(startA).getTime()
+ )
+ .map((timer) => (
+
+ ))
+ ) : (
+ <>>
+ )}
+
);
}
diff --git a/client/src/styles/index.css b/client/src/styles/index.css
index 71af95b..c4ac650 100644
--- a/client/src/styles/index.css
+++ b/client/src/styles/index.css
@@ -1,3 +1,49 @@
a {
cursor: pointer;
}
+
+nav {
+ margin-bottom: 1rem;
+}
+
+.my-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: start;
+}
+
+.card-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-gap: 1rem;
+}
+
+.grid-card {
+ display: flex;
+ flex-flow: column;
+ justify-content: space-between;
+}
+
+@media only screen and (max-width: 800px) {
+ .card-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media only screen and (max-width: 1200px) {
+ .card-grid {
+ grid-template-columns: 1fr 1fr 1fr;
+ }
+}
+
+.italic {
+ font-style: italic;
+}
+
+.timer-metadata {
+ padding-top: 0.75rem;
+}
+
+.container {
+ margin-bottom: 2rem;
+}
diff --git a/client/src/styles/modal.ts b/client/src/styles/modal.ts
index 09c383c..8022e4d 100644
--- a/client/src/styles/modal.ts
+++ b/client/src/styles/modal.ts
@@ -6,7 +6,7 @@ export default {
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)",
- width: "40vw",
- maxWidth: "800px",
+ width: "60vw",
+ maxWidth: "500px",
},
};
diff --git a/client/src/utils/types.ts b/client/src/utils/types.ts
index 4d9a0cd..0b7d932 100644
--- a/client/src/utils/types.ts
+++ b/client/src/utils/types.ts
@@ -10,11 +10,18 @@ export type Friend = {
export type TimerResponse = {
error?: string;
message?: string;
- id: number;
- name: string;
- start: Date;
- created_by: Friend;
- referenced_friends: Friend[];
+ id?: number;
+ name?: string;
+ start?: Date;
+ created_by?: Friend;
+ referenced_friends?: Friend[];
+ timer_refreshes?: TimerRefresh[];
+};
+
+export type TimerRefresh = {
+ start: string;
+ end: string;
+ refreshed_by: Friend;
};
export type TokenResponse = {
@@ -22,7 +29,7 @@ export type TokenResponse = {
message?: string;
token?: string;
expiration?: string;
- friend: Friend;
+ friend?: Friend;
};
export type SignThisTokenResponse = {
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 1d0c0e6..4c86353 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -31,7 +31,6 @@ services:
env_file: .env.dev
build:
context: ./server
- target: development
dockerfile: Dockerfile.dev
volumes:
- ./server:/usr/src/app
diff --git a/server/src/dto/dtos.ts b/server/src/dto/dtos.ts
index 90f21d7..db59d11 100644
--- a/server/src/dto/dtos.ts
+++ b/server/src/dto/dtos.ts
@@ -1,5 +1,5 @@
import { Type } from 'class-transformer';
-import { IsNotEmpty, ValidateIf } from 'class-validator';
+import { IsNotEmpty, ValidateIf, Max, Min, MaxLength } from 'class-validator';
export class SignedGodTokenDTO {
@IsNotEmpty()
@@ -17,10 +17,21 @@ export class RetrieveFriendDTO {
export class CreateTimerDTO {
@IsNotEmpty()
+ @MaxLength(80)
name: string;
}
-export class RefreshTimerDTO {
+export class RetrieveTimerDTO {
@Type(() => Number)
id: number;
}
+
+export class GetPageDTO {
+ @Type(() => Number)
+ @Max(500)
+ @Min(1)
+ take = 100;
+
+ @Type(() => Number)
+ skip = 0;
+}
diff --git a/server/src/timer/timer.controller.ts b/server/src/timer/timer.controller.ts
index ff277d6..95fb469 100644
--- a/server/src/timer/timer.controller.ts
+++ b/server/src/timer/timer.controller.ts
@@ -14,9 +14,10 @@ import { TimerService } from './timer.service';
import { TimerGateway } from './timer.gateway';
import { AuthService } from '../auth/auth.service';
import {
- RefreshTimerDTO,
+ RetrieveTimerDTO,
CreateTimerDTO,
RetrieveFriendDTO,
+ GetPageDTO,
} from '../dto/dtos';
import { AuthGuard } from 'src/auth/auth.guard';
import { Prisma } from '@prisma/client';
@@ -42,8 +43,22 @@ export class TimerController {
return await this.timerService.friendTimers(friend);
}
+ @Get('/:id/refreshes')
+ public async getTimerAndRefreshes(
+ @Param() { id }: RetrieveTimerDTO,
+ @Query() page: GetPageDTO,
+ ) {
+ const timer = await this.timerService.findTimerById(id);
+ if (!timer) throw new NotFoundException('No such timer with id');
+
+ return {
+ ...timer,
+ timer_refreshes: await this.timerService.getRefreshesPaged(timer, page),
+ };
+ }
+
@Post('/:id/refresh')
- public async refreshTimer(@Param() { id }: RefreshTimerDTO, @Req() req) {
+ public async refreshTimer(@Param() { id }: RetrieveTimerDTO, @Req() req) {
const timer = await this.timerService.findTimerById(id);
if (!timer) throw new NotFoundException('No such timer with id');
diff --git a/server/src/timer/timer.service.ts b/server/src/timer/timer.service.ts
index 36e6350..c4c379b 100644
--- a/server/src/timer/timer.service.ts
+++ b/server/src/timer/timer.service.ts
@@ -2,6 +2,7 @@ import { Friend, Timer, Prisma } from '@prisma/client';
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AuthService } from '../auth/auth.service';
+import { GetPageDTO } from 'src/dto/dtos';
@Injectable()
export class TimerService {
@@ -22,11 +23,35 @@ export class TimerService {
},
};
+ static REFRESHED_SELECT = {
+ id: true,
+ start: true,
+ end: true,
+ refreshed_by: {
+ select: AuthService.FRIEND_SELECT,
+ },
+ };
+
+ static LAST_REFRESH = {
+ orderBy: {
+ end: 'desc',
+ },
+ take: 1,
+ select: TimerService.REFRESHED_SELECT,
+ };
+
public getAll() {
return this.prismaService.timer.findMany({
select: {
...TimerService.TIMER_SELECT,
...TimerService.INCLUDE_FRIENDS_SELECT,
+ timer_refreshes: {
+ orderBy: {
+ end: 'desc',
+ },
+ take: 1,
+ select: TimerService.REFRESHED_SELECT,
+ },
},
});
}
@@ -36,6 +61,13 @@ export class TimerService {
select: {
...TimerService.TIMER_SELECT,
...TimerService.INCLUDE_FRIENDS_SELECT,
+ timer_refreshes: {
+ orderBy: {
+ end: 'desc',
+ },
+ take: 1,
+ select: TimerService.REFRESHED_SELECT,
+ },
},
where: {
referenced_friends: {
@@ -49,6 +81,19 @@ export class TimerService {
});
}
+ public async getRefreshesPaged(timer: Timer, { skip, take }: GetPageDTO) {
+ return this.prismaService.timerRefreshes.findMany({
+ take,
+ skip,
+ where: {
+ timer: {
+ id: timer.id,
+ },
+ },
+ select: TimerService.REFRESHED_SELECT,
+ });
+ }
+
public findTimerById(id: number) {
return this.prismaService.timer.findUnique({
where: { id },
@@ -57,17 +102,6 @@ export class TimerService {
public async refreshTimer(timer: Timer, friend: Friend) {
const now = new Date();
- const select = {
- ...TimerService.TIMER_SELECT,
- ...TimerService.INCLUDE_FRIENDS_SELECT,
- };
- const refreshedTimer = await this.prismaService.timer.update({
- where: { id: timer.id },
- data: {
- start: now,
- },
- select,
- });
await this.prismaService.timerRefreshes.create({
data: {
@@ -84,9 +118,26 @@ export class TimerService {
},
},
},
+ select: TimerService.REFRESHED_SELECT,
});
- return refreshedTimer;
+ return this.prismaService.timer.update({
+ where: { id: timer.id },
+ data: {
+ start: now,
+ },
+ select: {
+ ...TimerService.TIMER_SELECT,
+ ...TimerService.INCLUDE_FRIENDS_SELECT,
+ timer_refreshes: {
+ orderBy: {
+ end: 'desc',
+ },
+ take: 1,
+ select: TimerService.REFRESHED_SELECT,
+ },
+ },
+ });
}
public createTimerWithFriends(