resets
This commit is contained in:
parent
aac31071bf
commit
650acd8a72
3 changed files with 93 additions and 55 deletions
|
|
@ -44,8 +44,10 @@ model Base {
|
||||||
|
|
||||||
model TrophyReset {
|
model TrophyReset {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
date DateTime
|
||||||
trophiesAtStart Int
|
trophiesAtStart Int
|
||||||
trophiesLost Int
|
trophiesLost Int
|
||||||
|
numberOfDefenses Int
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
base Base @relation(fields: [baseId], references: [id])
|
base Base @relation(fields: [baseId], references: [id])
|
||||||
baseId String
|
baseId String
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,11 @@ app.get('/bases', requireAuth, async (req, res) => {
|
||||||
const bases = await prisma.base.findMany({
|
const bases = await prisma.base.findMany({
|
||||||
where: { userId: req.user.id },
|
where: { userId: req.user.id },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
|
include: {
|
||||||
|
trophyResets: {
|
||||||
|
orderBy: { date: 'desc' },
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
res.json({
|
res.json({
|
||||||
bases: bases.map((base) => ({
|
bases: bases.map((base) => ({
|
||||||
|
|
@ -281,6 +286,14 @@ app.get('/bases', requireAuth, async (req, res) => {
|
||||||
imageUrl: buildImageUrl(base),
|
imageUrl: buildImageUrl(base),
|
||||||
isPrivate: base.isPrivate,
|
isPrivate: base.isPrivate,
|
||||||
createdAt: base.createdAt,
|
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) => {
|
app.post('/bases/:baseId/trophy-resets', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { baseId } = req.params;
|
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 parsedTrophiesAtStart = Number(trophiesAtStart);
|
||||||
const parsedTrophiesLost = Number(trophiesLost);
|
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) {
|
if (!Number.isFinite(parsedTrophiesAtStart) || parsedTrophiesAtStart < 0) {
|
||||||
return res.status(400).json({ error: 'Trophies at start must be a positive number' });
|
return res.status(400).json({ error: 'Trophies at start must be a positive number' });
|
||||||
}
|
}
|
||||||
if (!Number.isFinite(parsedTrophiesLost)) {
|
if (!Number.isFinite(parsedTrophiesLost)) {
|
||||||
return res.status(400).json({ error: 'Trophies lost must be a number' });
|
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 } });
|
const base = await prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } });
|
||||||
if (!base) {
|
if (!base) {
|
||||||
|
|
@ -503,8 +524,10 @@ app.post('/bases/:baseId/trophy-resets', requireAuth, async (req, res) => {
|
||||||
await prisma.trophyReset.create({
|
await prisma.trophyReset.create({
|
||||||
data: {
|
data: {
|
||||||
baseId: base.id,
|
baseId: base.id,
|
||||||
|
date: parsedDate,
|
||||||
trophiesAtStart: parsedTrophiesAtStart,
|
trophiesAtStart: parsedTrophiesAtStart,
|
||||||
trophiesLost: parsedTrophiesLost,
|
trophiesLost: parsedTrophiesLost,
|
||||||
|
numberOfDefenses: parsedNumberOfDefenses,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -874,8 +897,10 @@ app.get('/profiles/:username', requireAuth, async (req, res) => {
|
||||||
}));
|
}));
|
||||||
const trophyResets = base.trophyResets.map((reset) => ({
|
const trophyResets = base.trophyResets.map((reset) => ({
|
||||||
id: reset.id,
|
id: reset.id,
|
||||||
|
date: reset.date,
|
||||||
trophiesAtStart: reset.trophiesAtStart,
|
trophiesAtStart: reset.trophiesAtStart,
|
||||||
trophiesLost: reset.trophiesLost,
|
trophiesLost: reset.trophiesLost,
|
||||||
|
numberOfDefenses: reset.numberOfDefenses,
|
||||||
createdAt: reset.createdAt,
|
createdAt: reset.createdAt,
|
||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,15 @@ type ArmyCategory = {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TrophyReset = {
|
||||||
|
id: string;
|
||||||
|
date: string;
|
||||||
|
trophiesAtStart: number;
|
||||||
|
trophiesLost: number;
|
||||||
|
numberOfDefenses: number;
|
||||||
|
createdAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
type BaseItem = {
|
type BaseItem = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -73,6 +82,7 @@ type BaseItem = {
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
isPrivate: boolean;
|
isPrivate: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
trophyResets: TrophyReset[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type DefenseItem = {
|
type DefenseItem = {
|
||||||
|
|
@ -105,13 +115,6 @@ type ProfileDefense = {
|
||||||
armyCategoryName: string;
|
armyCategoryName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TrophyReset = {
|
|
||||||
id: string;
|
|
||||||
trophiesAtStart: number;
|
|
||||||
trophiesLost: number;
|
|
||||||
createdAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ProfileBase = {
|
type ProfileBase = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -122,7 +125,6 @@ type ProfileBase = {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
summary: Summary;
|
summary: Summary;
|
||||||
defenses: ProfileDefense[];
|
defenses: ProfileDefense[];
|
||||||
trophyResets: TrophyReset[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProfileCategorySummary = Summary & {
|
type ProfileCategorySummary = Summary & {
|
||||||
|
|
@ -628,7 +630,7 @@ export default function Page() {
|
||||||
|
|
||||||
async function handleTrophyResetSubmit(event: FormEvent<HTMLFormElement>) {
|
async function handleTrophyResetSubmit(event: FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!profileSelectedBase) {
|
if (!selectedBaseId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const form = event.currentTarget;
|
const form = event.currentTarget;
|
||||||
|
|
@ -636,9 +638,9 @@ export default function Page() {
|
||||||
const payload = Object.fromEntries(formData.entries());
|
const payload = Object.fromEntries(formData.entries());
|
||||||
try {
|
try {
|
||||||
setErrors((prev) => ({ ...prev, trophyReset: '' }));
|
setErrors((prev) => ({ ...prev, trophyReset: '' }));
|
||||||
await request('POST', API.addTrophyReset(profileSelectedBase.id), payload);
|
await request('POST', API.addTrophyReset(selectedBaseId), payload);
|
||||||
form.reset();
|
form.reset();
|
||||||
await refreshOwnProfileDetail();
|
await refreshData();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setErrors((prev) => ({ ...prev, trophyReset: error.message }));
|
setErrors((prev) => ({ ...prev, trophyReset: error.message }));
|
||||||
}
|
}
|
||||||
|
|
@ -1542,6 +1544,52 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<h3>Legend League Day Reset</h3>
|
||||||
|
<form className="form compact" onSubmit={handleTrophyResetSubmit}>
|
||||||
|
<label>
|
||||||
|
Date
|
||||||
|
<input type="date" name="date" required />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Trophies at Start
|
||||||
|
<input type="number" name="trophiesAtStart" min={0} step={1} required className="styled-number" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Trophies Lost
|
||||||
|
<input type="number" name="trophiesLost" step={1} required className="styled-number" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Number of Defenses
|
||||||
|
<input type="number" name="numberOfDefenses" min={0} step={1} required className="styled-number" />
|
||||||
|
</label>
|
||||||
|
<button type="submit" className="primary">
|
||||||
|
Add Reset
|
||||||
|
</button>
|
||||||
|
<p className="form-error">{errors.trophyReset}</p>
|
||||||
|
</form>
|
||||||
|
<div className="subsection">
|
||||||
|
<h4>Recent Resets</h4>
|
||||||
|
<ul className="list compact">
|
||||||
|
{baseDetailMeta?.trophyResets?.length ? (
|
||||||
|
baseDetailMeta.trophyResets.map((reset) => (
|
||||||
|
<li key={reset.id} className="list-item">
|
||||||
|
<div className="defense-header">
|
||||||
|
<span>{new Date(reset.date).toLocaleDateString()}</span>
|
||||||
|
<div className="defense-meta">
|
||||||
|
<span>{reset.trophiesAtStart} trophies at start</span>
|
||||||
|
<span>{formatTrophies(reset.trophiesLost)} lost</span>
|
||||||
|
<span>{reset.numberOfDefenses} defenses</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<li>No resets logged yet.</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3>Defenses</h3>
|
<h3>Defenses</h3>
|
||||||
<ul id="base-detail-defenses" className="list">
|
<ul id="base-detail-defenses" className="list">
|
||||||
|
|
@ -1656,43 +1704,6 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="card">
|
|
||||||
<h3>Legend League Day Reset</h3>
|
|
||||||
<form className="form compact" onSubmit={handleTrophyResetSubmit}>
|
|
||||||
<label>
|
|
||||||
Trophies at Start
|
|
||||||
<input type="number" name="trophiesAtStart" min={0} step={1} required className="styled-number" />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Trophies Lost
|
|
||||||
<input type="number" name="trophiesLost" step={1} required className="styled-number" />
|
|
||||||
</label>
|
|
||||||
<button type="submit" className="primary">
|
|
||||||
Add Reset
|
|
||||||
</button>
|
|
||||||
<p className="form-error">{errors.trophyReset}</p>
|
|
||||||
</form>
|
|
||||||
<div className="subsection">
|
|
||||||
<h4>Recent Resets</h4>
|
|
||||||
<ul className="list compact">
|
|
||||||
{profileSelectedBase.trophyResets.length ? (
|
|
||||||
profileSelectedBase.trophyResets.map((reset) => (
|
|
||||||
<li key={reset.id} className="list-item">
|
|
||||||
<div className="defense-header">
|
|
||||||
<span>{new Date(reset.createdAt).toLocaleDateString()}</span>
|
|
||||||
<div className="defense-meta">
|
|
||||||
<span>{reset.trophiesAtStart} trophies at start</span>
|
|
||||||
<span>{formatTrophies(reset.trophiesLost)} lost</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<li>No resets logged yet.</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3>Defenses</h3>
|
<h3>Defenses</h3>
|
||||||
<ul className="list">
|
<ul className="list">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue