dev #1
					 2 changed files with 309 additions and 33 deletions
				
			
		|  | @ -589,6 +589,81 @@ app.post('/bases/:baseId/defenses', requireAuth, async (req, res) => { | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | app.put('/trophy-resets/:resetId', requireAuth, async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const { resetId } = req.params; | ||||||
|  |     const { date, trophiesAtStart, trophiesLost, numberOfDefenses, baseId } = 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' }); | ||||||
|  |     } | ||||||
|  |     if (!baseId) { | ||||||
|  |         return res.status(400).json({ error: 'Base is required' }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const reset = await prisma.trophyReset.findFirst({ | ||||||
|  |       where: { id: resetId, base: { userId: req.user.id } }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!reset) { | ||||||
|  |       return res.status(404).json({ error: 'Trophy reset not found' }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const base = await prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }); | ||||||
|  |     if (!base) { | ||||||
|  |         return res.status(404).json({ error: 'Base not found' }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await prisma.trophyReset.update({ | ||||||
|  |       where: { id: reset.id }, | ||||||
|  |       data: { | ||||||
|  |         date: parsedDate, | ||||||
|  |         trophiesAtStart: parsedTrophiesAtStart, | ||||||
|  |         trophiesLost: parsedTrophiesLost, | ||||||
|  |         numberOfDefenses: parsedNumberOfDefenses, | ||||||
|  |         baseId: base.id, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return res.json({ message: 'Trophy reset updated' }); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error(error); | ||||||
|  |     return res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | app.delete('/trophy-resets/:resetId', requireAuth, async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const { resetId } = req.params; | ||||||
|  |     const result = await prisma.trophyReset.deleteMany({ | ||||||
|  |       where: { id: resetId, base: { userId: req.user.id } }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!result.count) { | ||||||
|  |       return res.status(404).json({ error: 'Trophy reset not found' }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return res.json({ message: 'Trophy reset deleted' }); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error(error); | ||||||
|  |     return res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| app.put('/defenses/:defenseId', requireAuth, async (req, res) => { | app.put('/defenses/:defenseId', requireAuth, async (req, res) => { | ||||||
|   try { |   try { | ||||||
|     const { defenseId } = req.params; |     const { defenseId } = req.params; | ||||||
|  |  | ||||||
|  | @ -36,6 +36,8 @@ const API = { | ||||||
|   updateBase: (baseId: string) => `${API_BASE}/bases/${baseId}`, |   updateBase: (baseId: string) => `${API_BASE}/bases/${baseId}`, | ||||||
|   deleteBase: (baseId: string) => `${API_BASE}/bases/${baseId}`, |   deleteBase: (baseId: string) => `${API_BASE}/bases/${baseId}`, | ||||||
|   addTrophyReset: (baseId: string) => `${API_BASE}/bases/${baseId}/trophy-resets`, |   addTrophyReset: (baseId: string) => `${API_BASE}/bases/${baseId}/trophy-resets`, | ||||||
|  |   updateTrophyReset: (resetId: string) => `${API_BASE}/trophy-resets/${resetId}`, | ||||||
|  |   deleteTrophyReset: (resetId: string) => `${API_BASE}/trophy-resets/${resetId}`, | ||||||
|   updateDefense: (defenseId: string) => `${API_BASE}/defenses/${defenseId}`, |   updateDefense: (defenseId: string) => `${API_BASE}/defenses/${defenseId}`, | ||||||
|   deleteDefense: (defenseId: string) => `${API_BASE}/defenses/${defenseId}`, |   deleteDefense: (defenseId: string) => `${API_BASE}/defenses/${defenseId}`, | ||||||
|   profiles: `${API_BASE}/profiles`, |   profiles: `${API_BASE}/profiles`, | ||||||
|  | @ -125,6 +127,7 @@ type ProfileBase = { | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   summary: Summary; |   summary: Summary; | ||||||
|   defenses: ProfileDefense[]; |   defenses: ProfileDefense[]; | ||||||
|  |   trophyResets: TrophyReset[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type ProfileCategorySummary = Summary & { | type ProfileCategorySummary = Summary & { | ||||||
|  | @ -229,6 +232,7 @@ export default function Page() { | ||||||
|   const [editImageMode, setEditImageMode] = useState<'keep' | 'upload' | 'url' | 'remove'>('keep'); |   const [editImageMode, setEditImageMode] = useState<'keep' | 'upload' | 'url' | 'remove'>('keep'); | ||||||
|   const [editingBaseId, setEditingBaseId] = useState<string | null>(null); |   const [editingBaseId, setEditingBaseId] = useState<string | null>(null); | ||||||
|   const [editingDefenseId, setEditingDefenseId] = useState<string | null>(null); |   const [editingDefenseId, setEditingDefenseId] = useState<string | null>(null); | ||||||
|  |   const [editingTrophyResetId, setEditingTrophyResetId] = useState<string | null>(null); | ||||||
|   const [profileSearchTerm, setProfileSearchTerm] = useState(''); |   const [profileSearchTerm, setProfileSearchTerm] = useState(''); | ||||||
|   const [profileResults, setProfileResults] = useState<ProfileSummaryItem[]>([]); |   const [profileResults, setProfileResults] = useState<ProfileSummaryItem[]>([]); | ||||||
|   const [profileDetail, setProfileDetail] = useState<ProfileDetail | null>(null); |   const [profileDetail, setProfileDetail] = useState<ProfileDetail | null>(null); | ||||||
|  | @ -305,6 +309,12 @@ export default function Page() { | ||||||
|     return defenses.find((defense) => defense.id === editingDefenseId) ?? null; |     return defenses.find((defense) => defense.id === editingDefenseId) ?? null; | ||||||
|   }, [editingDefenseId, defenses]); |   }, [editingDefenseId, defenses]); | ||||||
| 
 | 
 | ||||||
|  |   const trophyResetBeingEdited = useMemo(() => { | ||||||
|  |     if (!editingTrophyResetId) return null; | ||||||
|  |     const allResets = bases.flatMap((base) => base.trophyResets); | ||||||
|  |     return allResets.find((reset) => reset.id === editingTrophyResetId) ?? null; | ||||||
|  |   }, [editingTrophyResetId, bases]); | ||||||
|  | 
 | ||||||
|   const profileSelectedBaseCategories = useMemo(() => { |   const profileSelectedBaseCategories = useMemo(() => { | ||||||
|     if (!profileSelectedBase) { |     if (!profileSelectedBase) { | ||||||
|       return [] as ProfileCategorySummary[]; |       return [] as ProfileCategorySummary[]; | ||||||
|  | @ -515,6 +525,15 @@ export default function Page() { | ||||||
|     setEditingDefenseId(null); |     setEditingDefenseId(null); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   function startEditingTrophyReset(resetId: string) { | ||||||
|  |     setEditingTrophyResetId(resetId); | ||||||
|  |     setErrors((prev) => ({ ...prev, trophyReset: '' })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function cancelEditingTrophyReset() { | ||||||
|  |     setEditingTrophyResetId(null); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async function handleDefenseEditSubmit(event: FormEvent<HTMLFormElement>) { |   async function handleDefenseEditSubmit(event: FormEvent<HTMLFormElement>) { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     if (!editingDefenseId || !defenseBeingEdited) { |     if (!editingDefenseId || !defenseBeingEdited) { | ||||||
|  | @ -551,6 +570,42 @@ export default function Page() { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async function handleTrophyResetEditSubmit(event: FormEvent<HTMLFormElement>) { | ||||||
|  |     event.preventDefault(); | ||||||
|  |     if (!editingTrophyResetId) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const form = event.currentTarget; | ||||||
|  |     const formData = new FormData(form); | ||||||
|  |     const payload = Object.fromEntries(formData.entries()); | ||||||
|  |     try { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: '' })); | ||||||
|  |       await request('PUT', API.updateTrophyReset(editingTrophyResetId), payload); | ||||||
|  |       setEditingTrophyResetId(null); | ||||||
|  |       await refreshData(); | ||||||
|  |       await refreshOwnProfileDetail(); | ||||||
|  |     } catch (error: any) { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: error.message })); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async function handleDeleteTrophyReset(resetId: string) { | ||||||
|  |     const confirmDelete = window.confirm('Delete this reset?'); | ||||||
|  |     if (!confirmDelete) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       await request('DELETE', API.deleteTrophyReset(resetId)); | ||||||
|  |       if (editingTrophyResetId === resetId) { | ||||||
|  |         setEditingTrophyResetId(null); | ||||||
|  |       } | ||||||
|  |       await refreshData(); | ||||||
|  |       await refreshOwnProfileDetail(); | ||||||
|  |     } catch (error: any) { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: error.message })); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async function handleProfileSearch(event: FormEvent<HTMLFormElement>) { |   async function handleProfileSearch(event: FormEvent<HTMLFormElement>) { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     setProfileError(''); |     setProfileError(''); | ||||||
|  | @ -630,15 +685,17 @@ export default function Page() { | ||||||
| 
 | 
 | ||||||
|   async function handleTrophyResetSubmit(event: FormEvent<HTMLFormElement>) { |   async function handleTrophyResetSubmit(event: FormEvent<HTMLFormElement>) { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     if (!selectedBaseId) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const form = event.currentTarget; |     const form = event.currentTarget; | ||||||
|     const formData = new FormData(form); |     const formData = new FormData(form); | ||||||
|     const payload = Object.fromEntries(formData.entries()); |     const payload = Object.fromEntries(formData.entries()); | ||||||
|  |     const baseId = payload.baseId as string; | ||||||
|  |     if (!baseId) { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: 'Base is required' })); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     try { |     try { | ||||||
|       setErrors((prev) => ({ ...prev, trophyReset: '' })); |       setErrors((prev) => ({ ...prev, trophyReset: '' })); | ||||||
|       await request('POST', API.addTrophyReset(selectedBaseId), payload); |       await request('POST', API.addTrophyReset(baseId), payload); | ||||||
|       form.reset(); |       form.reset(); | ||||||
|       await refreshData(); |       await refreshData(); | ||||||
|     } catch (error: any) { |     } catch (error: any) { | ||||||
|  | @ -1466,6 +1523,134 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|               </div> |               </div> | ||||||
|             )} |             )} | ||||||
|           </div> |           </div> | ||||||
|  |           <div className="card"> | ||||||
|  |             <h2>Log Legend League Day Reset</h2> | ||||||
|  |             <form id="trophy-reset-form" className="form compact" onSubmit={handleTrophyResetSubmit}> | ||||||
|  |               <label> | ||||||
|  |                 Base | ||||||
|  |                 <select name="baseId" required defaultValue=""> | ||||||
|  |                   <option value="" disabled> | ||||||
|  |                     {bases.length ? 'Select a base' : 'Add a base first'} | ||||||
|  |                   </option> | ||||||
|  |                   {bases.map((base) => ( | ||||||
|  |                     <option key={base.id} value={base.id}> | ||||||
|  |                       {base.title} | ||||||
|  |                     </option> | ||||||
|  |                   ))} | ||||||
|  |                 </select> | ||||||
|  |               </label> | ||||||
|  |               <label> | ||||||
|  |                 Date | ||||||
|  |                 <input type="date" name="date" required /> | ||||||
|  |               </label> | ||||||
|  |               <label> | ||||||
|  |                 Trophies | ||||||
|  |                 <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"> | ||||||
|  |                 Record Reset | ||||||
|  |               </button> | ||||||
|  |               <p className="form-error" data-for="trophyReset"> | ||||||
|  |                 {errors.trophyReset} | ||||||
|  |               </p> | ||||||
|  |             </form> | ||||||
|  |             <div className="subsection"> | ||||||
|  |               <details> | ||||||
|  |                 <summary><h3>Manage Resets</h3></summary> | ||||||
|  |                 <ul className="list compact"> | ||||||
|  |                   {bases.flatMap((base) => base.trophyResets).length ? ( | ||||||
|  |                     bases.flatMap((base) => base.trophyResets).slice(0, 10).map((reset) => ( | ||||||
|  |                       <li key={reset.id} className="list-item"> | ||||||
|  |                         <div className="defense-header"> | ||||||
|  |                           <div> | ||||||
|  |                             <strong>{bases.find((b) => b.id === bases.find((b) => b.trophyResets.some((r) => r.id === reset.id))?.id)?.title}</strong>{' '} | ||||||
|  |                             <span className="badge">{new Date(reset.date).toLocaleDateString()}</span> | ||||||
|  |                           </div> | ||||||
|  |                           <div className="defense-meta"> | ||||||
|  |                             <button | ||||||
|  |                               type="button" | ||||||
|  |                               className="ghost small" | ||||||
|  |                               onClick={() => startEditingTrophyReset(reset.id)} | ||||||
|  |                             > | ||||||
|  |                               Edit | ||||||
|  |                             </button> | ||||||
|  |                             <button | ||||||
|  |                               type="button" | ||||||
|  |                               className="ghost small" | ||||||
|  |                               onClick={() => handleDeleteTrophyReset(reset.id)} | ||||||
|  |                             > | ||||||
|  |                               Delete | ||||||
|  |                             </button> | ||||||
|  |                           </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div className="defense-meta"> | ||||||
|  |                           <span>{reset.trophiesAtStart} trophies</span> | ||||||
|  |                           <span>{formatTrophies(reset.trophiesLost)} lost</span> | ||||||
|  |                           <span>{reset.numberOfDefenses} defenses</span> | ||||||
|  |                         </div> | ||||||
|  |                       </li> | ||||||
|  |                     )) | ||||||
|  |                   ) : ( | ||||||
|  |                     <li>No resets logged yet.</li> | ||||||
|  |                   )} | ||||||
|  |                 </ul> | ||||||
|  |               </details> | ||||||
|  |             </div> | ||||||
|  |             {editingTrophyResetId && trophyResetBeingEdited && ( | ||||||
|  |               <div className="subsection"> | ||||||
|  |                 <h3>Edit Reset</h3> | ||||||
|  |                 <form | ||||||
|  |                   key={editingTrophyResetId} | ||||||
|  |                   className="form compact" | ||||||
|  |                   onSubmit={handleTrophyResetEditSubmit} | ||||||
|  |                 > | ||||||
|  |                   <label> | ||||||
|  |                     Base | ||||||
|  |                     <select name="baseId" required defaultValue={bases.find(b => b.trophyResets.some(r => r.id === editingTrophyResetId))?.id}> | ||||||
|  |                       {bases.map((base) => ( | ||||||
|  |                         <option key={base.id} value={base.id}> | ||||||
|  |                           {base.title} | ||||||
|  |                         </option> | ||||||
|  |                       ))} | ||||||
|  |                     </select> | ||||||
|  |                   </label> | ||||||
|  |                   <label> | ||||||
|  |                     Date | ||||||
|  |                     <input type="date" name="date" required defaultValue={new Date(trophyResetBeingEdited.date).toISOString().split('T')[0]} /> | ||||||
|  |                   </label> | ||||||
|  |                   <label> | ||||||
|  |                     Trophies | ||||||
|  |                     <input type="number" name="trophiesAtStart" min={0} step={1} required className="styled-number" defaultValue={trophyResetBeingEdited.trophiesAtStart} /> | ||||||
|  |                   </label> | ||||||
|  |                   <label> | ||||||
|  |                     Trophies Lost | ||||||
|  |                     <input type="number" name="trophiesLost" step={1} required className="styled-number" defaultValue={trophyResetBeingEdited.trophiesLost} /> | ||||||
|  |                   </label> | ||||||
|  |                   <label> | ||||||
|  |                     Number of Defenses | ||||||
|  |                     <input type="number" name="numberOfDefenses" min={0} step={1} required className="styled-number" defaultValue={trophyResetBeingEdited.numberOfDefenses} /> | ||||||
|  |                   </label> | ||||||
|  |                   <div className="defense-meta"> | ||||||
|  |                     <button type="submit" className="primary"> | ||||||
|  |                       Save Reset | ||||||
|  |                     </button> | ||||||
|  |                     <button type="button" className="ghost" onClick={cancelEditingTrophyReset}> | ||||||
|  |                       Cancel | ||||||
|  |                     </button> | ||||||
|  |                   </div> | ||||||
|  |                   <p className="form-error">{errors.trophyReset}</p> | ||||||
|  |                 </form> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|         </section> |         </section> | ||||||
| 
 | 
 | ||||||
|         <section id="base-detail-view" className={`view-section ${view !== 'baseDetail' ? 'hidden' : ''}`}> |         <section id="base-detail-view" className={`view-section ${view !== 'baseDetail' ? 'hidden' : ''}`}> | ||||||
|  | @ -1545,31 +1730,8 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|             </ul> |             </ul> | ||||||
|           </div> |           </div> | ||||||
|           <div className="card"> |           <div className="card"> | ||||||
|             <h3>Legend League Day Reset</h3> |             <details open> | ||||||
|             <form className="form compact" onSubmit={handleTrophyResetSubmit}> |               <summary><h3>Recent Resets</h3></summary> | ||||||
|               <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"> |               <ul className="list compact"> | ||||||
|                 {baseDetailMeta?.trophyResets?.length ? ( |                 {baseDetailMeta?.trophyResets?.length ? ( | ||||||
|                   baseDetailMeta.trophyResets.map((reset) => ( |                   baseDetailMeta.trophyResets.map((reset) => ( | ||||||
|  | @ -1577,18 +1739,34 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                       <div className="defense-header"> |                       <div className="defense-header"> | ||||||
|                         <span>{new Date(reset.date).toLocaleDateString()}</span> |                         <span>{new Date(reset.date).toLocaleDateString()}</span> | ||||||
|                         <div className="defense-meta"> |                         <div className="defense-meta"> | ||||||
|                           <span>{reset.trophiesAtStart} trophies at start</span> |                           <button | ||||||
|                           <span>{formatTrophies(reset.trophiesLost)} lost</span> |                             type="button" | ||||||
|                           <span>{reset.numberOfDefenses} defenses</span> |                             className="ghost small" | ||||||
|  |                             onClick={() => startEditingTrophyReset(reset.id)} | ||||||
|  |                           > | ||||||
|  |                             Edit | ||||||
|  |                           </button> | ||||||
|  |                           <button | ||||||
|  |                             type="button" | ||||||
|  |                             className="ghost small" | ||||||
|  |                             onClick={() => handleDeleteTrophyReset(reset.id)} | ||||||
|  |                           > | ||||||
|  |                             Delete | ||||||
|  |                           </button> | ||||||
|                         </div> |                         </div> | ||||||
|                       </div> |                       </div> | ||||||
|  |                       <div className="defense-meta"> | ||||||
|  |                         <span>{reset.trophiesAtStart} trophies at start</span> | ||||||
|  |                         <span>{formatTrophies(reset.trophiesLost)} lost</span> | ||||||
|  |                         <span>{reset.numberOfDefenses} defenses</span> | ||||||
|  |                       </div> | ||||||
|                     </li> |                     </li> | ||||||
|                   )) |                   )) | ||||||
|                 ) : ( |                 ) : ( | ||||||
|                   <li>No resets logged yet.</li> |                   <li>No resets logged yet.</li> | ||||||
|                 )} |                 )} | ||||||
|               </ul> |               </ul> | ||||||
|             </div> |             </details> | ||||||
|           </div> |           </div> | ||||||
|           <div className="card"> |           <div className="card"> | ||||||
|             <h3>Defenses</h3> |             <h3>Defenses</h3> | ||||||
|  | @ -1704,6 +1882,29 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                   )} |                   )} | ||||||
|                 </ul> |                 </ul> | ||||||
|               </div> |               </div> | ||||||
|  |               <div className="card"> | ||||||
|  |                 <details> | ||||||
|  |                   <summary><h3>Recent Resets</h3></summary> | ||||||
|  |                   <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.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> | ||||||
|  |                 </details> | ||||||
|  |               </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