-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2053 from GW2Treasures/feature/legendary-relics
Add page for legendary relics
- Loading branch information
Showing
7 changed files
with
258 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Scope } from '@gw2me/client'; | ||
|
||
export const requiredScopes = [ | ||
// always required | ||
Scope.GW2_Account, | ||
|
||
// get all the characters | ||
// TODO: remove once using armory subscription | ||
Scope.GW2_Characters, | ||
|
||
// get inventories | ||
// TODO: remove once using armory subscription | ||
Scope.GW2_Inventories, | ||
|
||
// legendary armory | ||
Scope.GW2_Unlocks, | ||
|
||
// delivered items waiting for pickup | ||
// TODO: remove once using armory subscription | ||
Scope.GW2_Tradingpost, | ||
|
||
// Relic unlocks using `/v2/account/achievements` | ||
Scope.GW2_Progression, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Gw2Accounts } from '@/components/Gw2Api/Gw2Accounts'; | ||
import { Trans } from '@/components/I18n/Trans'; | ||
import { HeroLayout } from '@/components/Layout/HeroLayout'; | ||
import { NavBar } from '@/components/Layout/NavBar'; | ||
import type { LayoutProps } from '@/lib/next'; | ||
import { Headline } from '@gw2treasures/ui/components/Headline/Headline'; | ||
import { requiredScopes } from './helper'; | ||
|
||
export default function LegendaryLayout({ children }: LayoutProps) { | ||
return ( | ||
<HeroLayout color="rgb(185 0 185)" | ||
hero={<Headline id="legendary-armory"><Trans id="legendary-armory"/></Headline>} | ||
navBar={( | ||
<NavBar base="/legendary/" items={[ | ||
{ segment: 'weapons', label: <Trans id="legendary-armory.weapons"/> }, | ||
{ segment: 'armor', label: <Trans id="legendary-armory.armor"/> }, | ||
{ segment: 'trinkets', label: <Trans id="legendary-armory.trinkets"/> }, | ||
{ segment: 'relics', label: <Trans id="legendary-armory.relics"/> }, | ||
]}/> | ||
)} | ||
> | ||
<> | ||
<Gw2Accounts requiredScopes={requiredScopes} loading={null} loginMessage={<Trans id="legendary-armory.login"/>} authorizationMessage={<Trans id="legendary-armory.authorize"/>}/> | ||
{children} | ||
</> | ||
</HeroLayout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Skeleton } from '@/components/Skeleton/Skeleton'; | ||
|
||
export default function LegendaryLoading() { | ||
return ( | ||
<Skeleton/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { AccountAchievementProgressHeader, AccountAchievementProgressRow } from '@/components/Achievement/AccountAchievementProgress'; | ||
import { AchievementLink } from '@/components/Achievement/AchievementLink'; | ||
import { Trans } from '@/components/I18n/Trans'; | ||
import { ItemLink } from '@/components/Item/ItemLink'; | ||
import { Description } from '@/components/Layout/Description'; | ||
import { ColumnSelect } from '@/components/Table/ColumnSelect'; | ||
import { cache } from '@/lib/cache'; | ||
import { linkProperties, linkPropertiesWithoutRarity } from '@/lib/linkProperties'; | ||
import type { PageProps } from '@/lib/next'; | ||
import { db } from '@/lib/prisma'; | ||
import { getTranslate } from '@/lib/translate'; | ||
import type { Achievement } from '@gw2api/types/data/achievement'; | ||
import { isDefined } from '@gw2treasures/helper/is'; | ||
import { Headline } from '@gw2treasures/ui/components/Headline/Headline'; | ||
import { createDataTable } from '@gw2treasures/ui/components/Table/DataTable'; | ||
import type { Metadata } from 'next'; | ||
import { createItemTable, LegendaryItemDataTable } from '../table'; | ||
|
||
// item id of the legendary relic | ||
const legendaryRelicId = 101582; | ||
|
||
// all the achievements are in this category | ||
const rareCollectionsAchievementCategoryId = 75; | ||
|
||
// core and SotO relics are always unlocked | ||
const coreAchievementIds = [ | ||
7685, // Relics—Core Set 1 | ||
7686, // Relics—Secrets of the Obscure Set 1 | ||
7684, // Relics—Secrets of the Obscure Set 2 | ||
7960, // Relics—Secrets of the Obscure Set 3 | ||
]; | ||
|
||
const loadItems = cache(async () => { | ||
const items = await db.item.findMany({ | ||
where: { id: legendaryRelicId }, | ||
select: linkProperties | ||
}); | ||
|
||
return items; | ||
}, ['legendary-relic'], { revalidate: 60 * 60 }); | ||
|
||
const loadAchievements = cache(async () => { | ||
const achievements = await db.achievement.findMany({ | ||
where: { | ||
name_en: { startsWith: 'Relics—%' }, | ||
id: { notIn: coreAchievementIds }, | ||
achievementCategoryId: rareCollectionsAchievementCategoryId | ||
}, | ||
select: { | ||
...linkPropertiesWithoutRarity, | ||
flags: true, | ||
prerequisitesIds: true, | ||
bitsItem: { select: linkProperties }, | ||
current_en: { select: { data: true }}, | ||
}, | ||
orderBy: { id: 'asc' }, | ||
}); | ||
|
||
return achievements; | ||
}, ['legendary-relics'], { revalidate: 60 * 60 }); | ||
|
||
function achievementsToRelics(achievements: Awaited<ReturnType<typeof loadAchievements>>) { | ||
return achievements.flatMap((achievement) => { | ||
const data = JSON.parse(achievement.current_en.data) as Achievement; | ||
|
||
return data.bits?.map((bit, index) => { | ||
if(bit.type !== 'Item') { | ||
return undefined; | ||
} | ||
|
||
const item = achievement.bitsItem.find(({ id }) => id === bit.id); | ||
|
||
return item ? { bitId: index, item, achievement } : undefined; | ||
}); | ||
}).filter(isDefined); | ||
} | ||
|
||
export default async function LegendaryRelicsPage() { | ||
const [items, achievements] = await Promise.all([ | ||
loadItems(), | ||
loadAchievements(), | ||
]); | ||
|
||
const Items = createItemTable(items); | ||
const Relics = createDataTable(achievementsToRelics(achievements), ({ item }) => item.id); | ||
|
||
return ( | ||
<> | ||
<Description actions={<ColumnSelect table={Items}/>}> | ||
<Trans id="legendary-armory.relics.description"/> | ||
</Description> | ||
<LegendaryItemDataTable table={Items}/> | ||
|
||
<Headline id="unlocks"><Trans id="legendary-armory.relics.unlocks"/></Headline> | ||
<p><Trans id="legendary-armory.relics.unlocks.description"/></p> | ||
<Relics.Table> | ||
<Relics.Column id="relic" title="Relic"> | ||
{({ item }) => <ItemLink item={item}/>} | ||
</Relics.Column> | ||
<Relics.Column id="set" title="Set"> | ||
{({ achievement }) => <AchievementLink achievement={achievement}/>} | ||
</Relics.Column> | ||
<Relics.DynamicColumns headers={<AccountAchievementProgressHeader/>}> | ||
{({ achievement, bitId }) => <AccountAchievementProgressRow achievement={achievement} bitId={bitId}/>} | ||
</Relics.DynamicColumns> | ||
</Relics.Table> | ||
</> | ||
); | ||
} | ||
|
||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> { | ||
const { language } = await params; | ||
const t = getTranslate(language); | ||
|
||
return { | ||
title: t('legendary-armory.relics.title'), | ||
description: t('legendary-armory.relics.description'), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use client'; | ||
|
||
import { ProgressCell } from '@/components/Achievement/ProgressCell'; | ||
import { FormatNumber } from '@/components/Format/FormatNumber'; | ||
import { useInventoryItem, UseInventoryItemAccountLocation } from '@/components/Inventory/use-inventory'; | ||
import { Skeleton } from '@/components/Skeleton/Skeleton'; | ||
import type { FC } from 'react'; | ||
|
||
interface LegendaryArmoryCellProps { | ||
itemId: number; | ||
accountId: string; | ||
} | ||
|
||
export const LegendaryArmoryCell: FC<LegendaryArmoryCellProps> = ({ itemId, accountId }) => { | ||
// TODO: only subscribe to legendary armory | ||
const inventory = useInventoryItem(accountId, itemId); | ||
|
||
if(inventory.loading) { | ||
return <td><Skeleton/></td>; | ||
} | ||
|
||
if(inventory.error) { | ||
return <td/>; | ||
} | ||
|
||
// get items in legendary armory | ||
const legendaryArmory = inventory.locations.find( | ||
({ location }) => location === UseInventoryItemAccountLocation.LegendaryArmory | ||
); | ||
|
||
// TODO use correct `max_count` | ||
const max_count = 1; | ||
|
||
return ( | ||
<ProgressCell progress={Math.min(legendaryArmory?.count ?? 0, 1)}> | ||
<FormatNumber value={legendaryArmory?.count ?? 0}/> / <FormatNumber value={max_count}/> | ||
</ProgressCell> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import type { FC } from 'react'; | ||
import type { LocalizedEntity } from '@/lib/localizedName'; | ||
import type { Item } from '@gw2treasures/database'; | ||
import { createDataTable } from '@gw2treasures/ui/components/Table/DataTable'; | ||
import { ItemLink } from '@/components/Item/ItemLink'; | ||
import { Gw2AccountBodyCells, Gw2AccountHeaderCells } from '@/components/Gw2Api/Gw2AccountTableCells'; | ||
import { LegendaryArmoryCell } from './table.client'; | ||
import { requiredScopes } from './helper'; | ||
import { Trans } from '@/components/I18n/Trans'; | ||
|
||
export function createItemTable(items: Pick<Item, keyof LocalizedEntity | 'id' | 'rarity'>[]) { | ||
return createDataTable(items, ({ id }) => id); | ||
} | ||
|
||
interface LegendaryItemDataTableProps { | ||
table: ReturnType<typeof createItemTable> | ||
} | ||
|
||
export const LegendaryItemDataTable: FC<LegendaryItemDataTableProps> = ({ table: items }) => { | ||
return ( | ||
<items.Table> | ||
<items.Column id="id" title={<Trans id="itemTable.column.id"/>} small hidden align="right">{({ id }) => id}</items.Column> | ||
<items.Column id="item" title={<Trans id="itemTable.column.item"/>} fixed>{(item) => <ItemLink item={item}/>}</items.Column> | ||
<items.DynamicColumns headers={<Gw2AccountHeaderCells small requiredScopes={requiredScopes}/>}> | ||
{(item) => <Gw2AccountBodyCells requiredScopes={requiredScopes}><LegendaryArmoryCell itemId={item.id} accountId={undefined as never}/></Gw2AccountBodyCells>} | ||
</items.DynamicColumns> | ||
</items.Table> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters