dev #1
					 3 changed files with 166 additions and 36 deletions
				
			
		|  | @ -39,6 +39,16 @@ model Base { | ||||||
|   user        User     @relation(fields: [userId], references: [id]) |   user        User     @relation(fields: [userId], references: [id]) | ||||||
|   userId      String |   userId      String | ||||||
|   defenses    Defense[] |   defenses    Defense[] | ||||||
|  |   trophyResets TrophyReset[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | model TrophyReset { | ||||||
|  |   id              String   @id @default(cuid()) | ||||||
|  |   trophiesAtStart Int | ||||||
|  |   trophiesLost    Int | ||||||
|  |   createdAt       DateTime @default(now()) | ||||||
|  |   base            Base     @relation(fields: [baseId], references: [id]) | ||||||
|  |   baseId          String | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| model Defense { | model Defense { | ||||||
|  | @ -49,6 +59,6 @@ model Defense { | ||||||
|   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 | ||||||
|   armyCategory   ArmyCategory  @relation(fields: [armyCategoryId], references: [id]) |   armyCategory   ArmyCategory? @relation(fields: [armyCategoryId], references: [id]) | ||||||
|   armyCategoryId String |   armyCategoryId String? | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -480,6 +480,41 @@ 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 parsedTrophiesAtStart = Number(trophiesAtStart); | ||||||
|  |     const parsedTrophiesLost = Number(trophiesLost); | ||||||
|  | 
 | ||||||
|  |     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' }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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.create({ | ||||||
|  |       data: { | ||||||
|  |         baseId: base.id, | ||||||
|  |         trophiesAtStart: parsedTrophiesAtStart, | ||||||
|  |         trophiesLost: parsedTrophiesLost, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return res.status(201).json({ message: 'Trophy reset logged' }); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error(error); | ||||||
|  |     return res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| app.post('/bases/:baseId/defenses', requireAuth, async (req, res) => { | app.post('/bases/:baseId/defenses', requireAuth, async (req, res) => { | ||||||
|   try { |   try { | ||||||
|     const { baseId } = req.params; |     const { baseId } = req.params; | ||||||
|  | @ -489,9 +524,6 @@ app.post('/bases/:baseId/defenses', requireAuth, async (req, res) => { | ||||||
|     const parsedPercent = Number(percent); |     const parsedPercent = Number(percent); | ||||||
|     const parsedTrophies = Number(trophies ?? 0); |     const parsedTrophies = Number(trophies ?? 0); | ||||||
| 
 | 
 | ||||||
|     if (!armyCategoryId) { |  | ||||||
|       return res.status(400).json({ error: 'Army category is required' }); |  | ||||||
|     } |  | ||||||
|     if (!Number.isFinite(parsedStars) || parsedStars < 0 || parsedStars > 3) { |     if (!Number.isFinite(parsedStars) || parsedStars < 0 || parsedStars > 3) { | ||||||
|       return res.status(400).json({ error: 'Stars must be between 0 and 3' }); |       return res.status(400).json({ error: 'Stars must be between 0 and 3' }); | ||||||
|     } |     } | ||||||
|  | @ -502,22 +534,25 @@ app.post('/bases/:baseId/defenses', requireAuth, async (req, res) => { | ||||||
|       return res.status(400).json({ error: 'Trophies must be between -200 and 200' }); |       return res.status(400).json({ error: 'Trophies must be between -200 and 200' }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const [base, category] = await Promise.all([ |     const base = await prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }); | ||||||
|       prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }), |  | ||||||
|       prisma.armyCategory.findFirst({ where: { id: armyCategoryId, userId: req.user.id } }), |  | ||||||
|     ]); |  | ||||||
| 
 |  | ||||||
|     if (!base) { |     if (!base) { | ||||||
|       return res.status(404).json({ error: 'Base not found' }); |       return res.status(404).json({ error: 'Base not found' }); | ||||||
|     } |     } | ||||||
|     if (!category) { | 
 | ||||||
|       return res.status(404).json({ error: 'Army category not found' }); |     let category = null; | ||||||
|  |     if (armyCategoryId) { | ||||||
|  |       category = await prisma.armyCategory.findFirst({ | ||||||
|  |         where: { id: armyCategoryId, userId: req.user.id }, | ||||||
|  |       }); | ||||||
|  |       if (!category) { | ||||||
|  |         return res.status(404).json({ error: 'Army category not found' }); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     await prisma.defense.create({ |     await prisma.defense.create({ | ||||||
|       data: { |       data: { | ||||||
|         baseId: base.id, |         baseId: base.id, | ||||||
|         armyCategoryId: category.id, |         armyCategoryId: category ? category.id : null, | ||||||
|         stars: parsedStars, |         stars: parsedStars, | ||||||
|         percent: parsedPercent, |         percent: parsedPercent, | ||||||
|         trophies: parsedTrophies, |         trophies: parsedTrophies, | ||||||
|  | @ -539,9 +574,6 @@ app.put('/defenses/:defenseId', requireAuth, async (req, res) => { | ||||||
|     if (!baseId) { |     if (!baseId) { | ||||||
|       return res.status(400).json({ error: 'Base is required' }); |       return res.status(400).json({ error: 'Base is required' }); | ||||||
|     } |     } | ||||||
|     if (!armyCategoryId) { |  | ||||||
|       return res.status(400).json({ error: 'Army category is required' }); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const parsedStars = Number(stars); |     const parsedStars = Number(stars); | ||||||
|     const parsedPercent = Number(percent); |     const parsedPercent = Number(percent); | ||||||
|  | @ -557,29 +589,34 @@ app.put('/defenses/:defenseId', requireAuth, async (req, res) => { | ||||||
|       return res.status(400).json({ error: 'Trophies must be between -200 and 200' }); |       return res.status(400).json({ error: 'Trophies must be between -200 and 200' }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const [defense, base, category] = await Promise.all([ |     const defense = await prisma.defense.findFirst({ | ||||||
|       prisma.defense.findFirst({ |       where: { id: defenseId, base: { userId: req.user.id } }, | ||||||
|         where: { id: defenseId, base: { userId: req.user.id } }, |     }); | ||||||
|       }), |  | ||||||
|       prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }), |  | ||||||
|       prisma.armyCategory.findFirst({ where: { id: armyCategoryId, userId: req.user.id } }), |  | ||||||
|     ]); |  | ||||||
| 
 | 
 | ||||||
|     if (!defense) { |     if (!defense) { | ||||||
|       return res.status(404).json({ error: 'Defense not found' }); |       return res.status(404).json({ error: 'Defense not found' }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     const base = await prisma.base.findFirst({ where: { id: baseId, userId: req.user.id } }); | ||||||
|     if (!base) { |     if (!base) { | ||||||
|       return res.status(404).json({ error: 'Base not found' }); |       return res.status(404).json({ error: 'Base not found' }); | ||||||
|     } |     } | ||||||
|     if (!category) { | 
 | ||||||
|       return res.status(404).json({ error: 'Army category not found' }); |     let category = null; | ||||||
|  |     if (armyCategoryId) { | ||||||
|  |       category = await prisma.armyCategory.findFirst({ | ||||||
|  |         where: { id: armyCategoryId, userId: req.user.id }, | ||||||
|  |       }); | ||||||
|  |       if (!category) { | ||||||
|  |         return res.status(404).json({ error: 'Army category not found' }); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     await prisma.defense.update({ |     await prisma.defense.update({ | ||||||
|       where: { id: defense.id }, |       where: { id: defense.id }, | ||||||
|       data: { |       data: { | ||||||
|         baseId: base.id, |         baseId: base.id, | ||||||
|         armyCategoryId: category.id, |         armyCategoryId: armyCategoryId ? category.id : null, | ||||||
|         stars: parsedStars, |         stars: parsedStars, | ||||||
|         percent: parsedPercent, |         percent: parsedPercent, | ||||||
|         trophies: parsedTrophies, |         trophies: parsedTrophies, | ||||||
|  | @ -628,10 +665,16 @@ app.get('/defenses', requireAuth, async (req, res) => { | ||||||
|     const categoryLookup = new Map( |     const categoryLookup = new Map( | ||||||
|       user.armyCategories.map((category) => [category.id, category.name]) |       user.armyCategories.map((category) => [category.id, category.name]) | ||||||
|     ); |     ); | ||||||
|  |     categoryLookup.set(null, '(No category)'); | ||||||
| 
 | 
 | ||||||
|     const defenses = []; |     const defenses = []; | ||||||
|     const baseBuckets = new Map(); |     const baseBuckets = new Map(); | ||||||
|     const categoryBuckets = new Map(); |     const categoryBuckets = new Map(); | ||||||
|  |     categoryBuckets.set(null, { | ||||||
|  |       name: '(No category)', | ||||||
|  |       items: [], | ||||||
|  |       bases: new Map(), | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     user.bases.forEach((base) => { |     user.bases.forEach((base) => { | ||||||
|       baseBuckets.set(base.id, { |       baseBuckets.set(base.id, { | ||||||
|  | @ -813,6 +856,9 @@ app.get('/profiles/:username', requireAuth, async (req, res) => { | ||||||
|             }, |             }, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|  |         trophyResets: { | ||||||
|  |           orderBy: { createdAt: 'desc' }, | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | @ -824,7 +870,13 @@ app.get('/profiles/:username', requireAuth, async (req, res) => { | ||||||
|         trophies: defense.trophies, |         trophies: defense.trophies, | ||||||
|         createdAt: defense.createdAt, |         createdAt: defense.createdAt, | ||||||
|         armyCategoryId: defense.armyCategoryId, |         armyCategoryId: defense.armyCategoryId, | ||||||
|         armyCategoryName: defense.armyCategory?.name || 'Unknown Army', |         armyCategoryName: defense.armyCategory?.name || '(No category)', | ||||||
|  |       })); | ||||||
|  |       const trophyResets = base.trophyResets.map((reset) => ({ | ||||||
|  |         id: reset.id, | ||||||
|  |         trophiesAtStart: reset.trophiesAtStart, | ||||||
|  |         trophiesLost: reset.trophiesLost, | ||||||
|  |         createdAt: reset.createdAt, | ||||||
|       })); |       })); | ||||||
|       return { |       return { | ||||||
|         id: base.id, |         id: base.id, | ||||||
|  | @ -836,6 +888,7 @@ app.get('/profiles/:username', requireAuth, async (req, res) => { | ||||||
|         createdAt: base.createdAt, |         createdAt: base.createdAt, | ||||||
|         summary: summarizeDefenses(defenses), |         summary: summarizeDefenses(defenses), | ||||||
|         defenses, |         defenses, | ||||||
|  |         trophyResets, | ||||||
|       }; |       }; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ const API = { | ||||||
|   deleteCategory: (categoryId: string) => `${API_BASE}/army-categories/${categoryId}`, |   deleteCategory: (categoryId: string) => `${API_BASE}/army-categories/${categoryId}`, | ||||||
|   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`, | ||||||
|   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`, | ||||||
|  | @ -104,6 +105,13 @@ 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; | ||||||
|  | @ -114,6 +122,7 @@ type ProfileBase = { | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   summary: Summary; |   summary: Summary; | ||||||
|   defenses: ProfileDefense[]; |   defenses: ProfileDefense[]; | ||||||
|  |   trophyResets: TrophyReset[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type ProfileCategorySummary = Summary & { | type ProfileCategorySummary = Summary & { | ||||||
|  | @ -165,6 +174,7 @@ type ErrorState = { | ||||||
|   category: string; |   category: string; | ||||||
|   base: string; |   base: string; | ||||||
|   defense: string; |   defense: string; | ||||||
|  |   trophyReset: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| async function request(method: string, url: string, body?: any, options?: { isForm?: boolean }) { | async function request(method: string, url: string, body?: any, options?: { isForm?: boolean }) { | ||||||
|  | @ -197,6 +207,7 @@ const initialErrors: ErrorState = { | ||||||
|   category: '', |   category: '', | ||||||
|   base: '', |   base: '', | ||||||
|   defense: '', |   defense: '', | ||||||
|  |   trophyReset: '', | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function Page() { | export default function Page() { | ||||||
|  | @ -300,7 +311,7 @@ export default function Page() { | ||||||
|     profileSelectedBase.defenses.forEach((defense) => { |     profileSelectedBase.defenses.forEach((defense) => { | ||||||
|       const key = defense.armyCategoryId || defense.armyCategoryName; |       const key = defense.armyCategoryId || defense.armyCategoryName; | ||||||
|       if (!buckets.has(key)) { |       if (!buckets.has(key)) { | ||||||
|         buckets.set(key, { name: defense.armyCategoryName || 'Unknown Army', items: [] }); |         buckets.set(key, { name: defense.armyCategoryName || '(No category)', items: [] }); | ||||||
|       } |       } | ||||||
|       buckets.get(key)!.items.push(defense); |       buckets.get(key)!.items.push(defense); | ||||||
|     }); |     }); | ||||||
|  | @ -615,6 +626,24 @@ export default function Page() { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async function handleTrophyResetSubmit(event: FormEvent<HTMLFormElement>) { | ||||||
|  |     event.preventDefault(); | ||||||
|  |     if (!profileSelectedBase) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const form = event.currentTarget; | ||||||
|  |     const formData = new FormData(form); | ||||||
|  |     const payload = Object.fromEntries(formData.entries()); | ||||||
|  |     try { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: '' })); | ||||||
|  |       await request('POST', API.addTrophyReset(profileSelectedBase.id), payload); | ||||||
|  |       form.reset(); | ||||||
|  |       await refreshOwnProfileDetail(); | ||||||
|  |     } catch (error: any) { | ||||||
|  |       setErrors((prev) => ({ ...prev, trophyReset: error.message })); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async function handleLogout() { |   async function handleLogout() { | ||||||
|     try { |     try { | ||||||
|       await request('POST', API.logout); |       await request('POST', API.logout); | ||||||
|  | @ -985,7 +1014,7 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|               {defenses.length ? ( |               {defenses.length ? ( | ||||||
|                 defenses.map((defense) => { |                 defenses.map((defense) => { | ||||||
|                   const date = new Date(defense.createdAt); |                   const date = new Date(defense.createdAt); | ||||||
|                   const categoryName = categoryNameMap.get(defense.armyCategoryId) || 'Unknown Army'; |                   const categoryName = categoryNameMap.get(defense.armyCategoryId) || '(No category)'; | ||||||
|                   return ( |                   return ( | ||||||
|                     <li key={defense.id} className="list-item"> |                     <li key={defense.id} className="list-item"> | ||||||
|                       <div className="defense-header"> |                       <div className="defense-header"> | ||||||
|  | @ -1263,9 +1292,9 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|               </label> |               </label> | ||||||
|               <label> |               <label> | ||||||
|                 Army Category |                 Army Category | ||||||
|                 <select name="armyCategoryId" required defaultValue=""> |                 <select name="armyCategoryId" defaultValue=""> | ||||||
|                   <option value="" disabled> |                   <option value=""> | ||||||
|                     {categories.length ? 'Select an army category' : 'Add an army category first'} |                     {categories.length ? '(No category)' : 'Add an army category first'} | ||||||
|                   </option> |                   </option> | ||||||
|                   {categories.map((category) => ( |                   {categories.map((category) => ( | ||||||
|                     <option key={category.id} value={category.id}> |                     <option key={category.id} value={category.id}> | ||||||
|  | @ -1308,7 +1337,7 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                 {defenses.length ? ( |                 {defenses.length ? ( | ||||||
|                   defenses.slice(0, 10).map((defense) => { |                   defenses.slice(0, 10).map((defense) => { | ||||||
|                     const categoryName = |                     const categoryName = | ||||||
|                       categoryNameMap.get(defense.armyCategoryId) || defense.categoryName || 'Unknown Army'; |                       categoryNameMap.get(defense.armyCategoryId) || defense.categoryName || '(No category)'; | ||||||
|                     return ( |                     return ( | ||||||
|                       <li key={defense.id} className="list-item"> |                       <li key={defense.id} className="list-item"> | ||||||
|                         <div className="defense-header"> |                         <div className="defense-header"> | ||||||
|  | @ -1370,7 +1399,8 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                   </label> |                   </label> | ||||||
|                   <label> |                   <label> | ||||||
|                     Army Category |                     Army Category | ||||||
|                     <select name="armyCategoryId" required defaultValue={defenseBeingEdited.armyCategoryId}> |                     <select name="armyCategoryId" defaultValue={defenseBeingEdited.armyCategoryId || ''}> | ||||||
|  |                       <option value="">(No category)</option> | ||||||
|                       {categories.map((category) => ( |                       {categories.map((category) => ( | ||||||
|                         <option key={category.id} value={category.id}> |                         <option key={category.id} value={category.id}> | ||||||
|                           {category.name} |                           {category.name} | ||||||
|  | @ -1520,7 +1550,7 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                   .filter((defense) => defense.baseId === selectedBaseId) |                   .filter((defense) => defense.baseId === selectedBaseId) | ||||||
|                   .map((defense) => { |                   .map((defense) => { | ||||||
|                     const date = new Date(defense.createdAt); |                     const date = new Date(defense.createdAt); | ||||||
|                     const categoryName = categoryNameMap.get(defense.armyCategoryId) || 'Unknown Army'; |                     const categoryName = categoryNameMap.get(defense.armyCategoryId) || '(No category)'; | ||||||
|                     return ( |                     return ( | ||||||
|                       <li key={defense.id} className="list-item"> |                       <li key={defense.id} className="list-item"> | ||||||
|                         <div className="defense-header"> |                         <div className="defense-header"> | ||||||
|  | @ -1626,6 +1656,43 @@ 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"> | ||||||
|  | @ -1633,7 +1700,7 @@ function summarizeProfileDefenses(defenses: ProfileDefense[]): Summary { | ||||||
|                     profileSelectedBase.defenses.map((defense) => ( |                     profileSelectedBase.defenses.map((defense) => ( | ||||||
|                       <li key={defense.id} className="list-item"> |                       <li key={defense.id} className="list-item"> | ||||||
|                         <div className="defense-header"> |                         <div className="defense-header"> | ||||||
|                           <strong>{defense.armyCategoryName || 'Unknown Army'}</strong> |                           <strong>{defense.armyCategoryName || '(No category)'}</strong> | ||||||
|                           <div> |                           <div> | ||||||
|                             <strong>{defense.stars}★</strong> • {defense.percent}% • {formatTrophies(defense.trophies)} |                             <strong>{defense.stars}★</strong> • {defense.percent}% • {formatTrophies(defense.trophies)} | ||||||
|                           </div> |                           </div> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue