From 650acd8a72dd5db82b14c6d8e1ea660441445c5b Mon Sep 17 00:00:00 2001 From: Hymmel Date: Wed, 15 Oct 2025 14:06:49 +0200 Subject: [PATCH] resets --- backend/prisma/schema.prisma | 14 +++-- backend/src/server.js | 27 ++++++++- frontend/app/page.tsx | 107 +++++++++++++++++++---------------- 3 files changed, 93 insertions(+), 55 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 4195f09..cb2924b 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -43,12 +43,14 @@ model Base { } model TrophyReset { - id String @id @default(cuid()) - trophiesAtStart Int - trophiesLost Int - createdAt DateTime @default(now()) - base Base @relation(fields: [baseId], references: [id]) - baseId String + id String @id @default(cuid()) + date DateTime + trophiesAtStart Int + trophiesLost Int + numberOfDefenses Int + createdAt DateTime @default(now()) + base Base @relation(fields: [baseId], references: [id]) + baseId String } model Defense { diff --git a/backend/src/server.js b/backend/src/server.js index e2e7240..e29d6e5 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -271,6 +271,11 @@ app.get('/bases', requireAuth, async (req, res) => { const bases = await prisma.base.findMany({ where: { userId: req.user.id }, orderBy: { createdAt: 'desc' }, + include: { + trophyResets: { + orderBy: { date: 'desc' }, + }, + }, }); res.json({ bases: bases.map((base) => ({ @@ -281,6 +286,14 @@ app.get('/bases', requireAuth, async (req, res) => { imageUrl: buildImageUrl(base), isPrivate: base.isPrivate, createdAt: base.createdAt, + trophyResets: base.trophyResets.map((reset) => ({ + id: reset.id, + date: reset.date, + trophiesAtStart: reset.trophiesAtStart, + trophiesLost: reset.trophiesLost, + numberOfDefenses: reset.numberOfDefenses, + createdAt: reset.createdAt, + })), })), }); }); @@ -483,17 +496,25 @@ app.delete('/bases/:baseId', requireAuth, async (req, res) => { app.post('/bases/:baseId/trophy-resets', requireAuth, async (req, res) => { try { const { baseId } = req.params; - const { trophiesAtStart, trophiesLost } = req.body || {}; + const { date, trophiesAtStart, trophiesLost, numberOfDefenses } = req.body || {}; + const parsedDate = new Date(date); const parsedTrophiesAtStart = Number(trophiesAtStart); const parsedTrophiesLost = Number(trophiesLost); + const parsedNumberOfDefenses = Number(numberOfDefenses); + if (isNaN(parsedDate.getTime())) { + return res.status(400).json({ error: 'Invalid date' }); + } if (!Number.isFinite(parsedTrophiesAtStart) || parsedTrophiesAtStart < 0) { return res.status(400).json({ error: 'Trophies at start must be a positive number' }); } if (!Number.isFinite(parsedTrophiesLost)) { return res.status(400).json({ error: 'Trophies lost must be a number' }); } + if (!Number.isFinite(parsedNumberOfDefenses) || parsedNumberOfDefenses < 0) { + return res.status(400).json({ error: 'Number of defenses must be a positive number' }); + } const base = await prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }); if (!base) { @@ -503,8 +524,10 @@ app.post('/bases/:baseId/trophy-resets', requireAuth, async (req, res) => { await prisma.trophyReset.create({ data: { baseId: base.id, + date: parsedDate, trophiesAtStart: parsedTrophiesAtStart, trophiesLost: parsedTrophiesLost, + numberOfDefenses: parsedNumberOfDefenses, }, }); @@ -874,8 +897,10 @@ app.get('/profiles/:username', requireAuth, async (req, res) => { })); const trophyResets = base.trophyResets.map((reset) => ({ id: reset.id, + date: reset.date, trophiesAtStart: reset.trophiesAtStart, trophiesLost: reset.trophiesLost, + numberOfDefenses: reset.numberOfDefenses, createdAt: reset.createdAt, })); return { diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index fe40b2b..8accd48 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -65,6 +65,15 @@ type ArmyCategory = { createdAt: string; }; +type TrophyReset = { + id: string; + date: string; + trophiesAtStart: number; + trophiesLost: number; + numberOfDefenses: number; + createdAt: string; +}; + type BaseItem = { id: string; title: string; @@ -73,6 +82,7 @@ type BaseItem = { imageUrl: string; isPrivate: boolean; createdAt: string; + trophyResets: TrophyReset[]; }; type DefenseItem = { @@ -105,13 +115,6 @@ type ProfileDefense = { armyCategoryName: string; }; -type TrophyReset = { - id: string; - trophiesAtStart: number; - trophiesLost: number; - createdAt: string; -}; - type ProfileBase = { id: string; title: string; @@ -122,7 +125,6 @@ type ProfileBase = { createdAt: string; summary: Summary; defenses: ProfileDefense[]; - trophyResets: TrophyReset[]; }; type ProfileCategorySummary = Summary & { @@ -628,7 +630,7 @@ export default function Page() { async function handleTrophyResetSubmit(event: FormEvent) { event.preventDefault(); - if (!profileSelectedBase) { + if (!selectedBaseId) { return; } const form = event.currentTarget; @@ -636,9 +638,9 @@ export default function Page() { const payload = Object.fromEntries(formData.entries()); try { setErrors((prev) => ({ ...prev, trophyReset: '' })); - await request('POST', API.addTrophyReset(profileSelectedBase.id), payload); + await request('POST', API.addTrophyReset(selectedBaseId), payload); form.reset(); - await refreshOwnProfileDetail(); + await refreshData(); } catch (error: any) { setErrors((prev) => ({ ...prev, trophyReset: error.message })); } @@ -1542,6 +1544,52 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { )} +
+

Legend League Day Reset

+
+ + + + + +

{errors.trophyReset}

+
+
+

Recent Resets

+
    + {baseDetailMeta?.trophyResets?.length ? ( + baseDetailMeta.trophyResets.map((reset) => ( +
  • +
    + {new Date(reset.date).toLocaleDateString()} +
    + {reset.trophiesAtStart} trophies at start + {formatTrophies(reset.trophiesLost)} lost + {reset.numberOfDefenses} defenses +
    +
    +
  • + )) + ) : ( +
  • No resets logged yet.
  • + )} +
+
+

Defenses

    @@ -1656,43 +1704,6 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { )}
-
-

Legend League Day Reset

-
- - - -

{errors.trophyReset}

-
-
-

Recent Resets

-
    - {profileSelectedBase.trophyResets.length ? ( - profileSelectedBase.trophyResets.map((reset) => ( -
  • -
    - {new Date(reset.createdAt).toLocaleDateString()} -
    - {reset.trophiesAtStart} trophies at start - {formatTrophies(reset.trophiesLost)} lost -
    -
    -
  • - )) - ) : ( -
  • No resets logged yet.
  • - )} -
-
-

Defenses