diff --git a/apps/web/app/[language]/festival/wintersday/(index)/page.tsx b/apps/web/app/[language]/festival/wintersday/(index)/page.tsx index ab3b5d84e..2e64f7b04 100644 --- a/apps/web/app/[language]/festival/wintersday/(index)/page.tsx +++ b/apps/web/app/[language]/festival/wintersday/(index)/page.tsx @@ -1,4 +1,6 @@ +import { Gw2Accounts } from '@/components/Gw2Api/Gw2Accounts'; import { Trans } from '@/components/I18n/Trans'; +import { ItemInventoryTable } from '@/components/Item/ItemInventoryTable'; import { ItemTable } from '@/components/ItemTable/ItemTable'; import { ItemTableColumnsButton } from '@/components/ItemTable/ItemTableColumnsButton'; import { ItemTableContext } from '@/components/ItemTable/ItemTableContext'; @@ -6,6 +8,14 @@ import { PageLayout } from '@/components/Layout/PageLayout'; import { pageView } from '@/lib/pageView'; import { Headline } from '@gw2treasures/ui/components/Headline/Headline'; import type { Metadata } from 'next'; +import { requiredScopes } from '../helper'; +import { db } from '@/lib/prisma'; +import { linkProperties } from '@/lib/linkProperties'; +import { cache } from '@/lib/cache'; +import { ItemLink } from '@/components/Item/ItemLink'; +import type { PageProps } from '@/lib/next'; +import { getTranslate } from '@/lib/translate'; +import { Fragment } from 'react'; const itemIds = [ 86601, @@ -13,20 +23,51 @@ const itemIds = [ 77604, ]; +const loadData = cache(async function loadData() { + const [items] = await Promise.all([ + db.item.findMany({ + where: { id: { in: itemIds }}, + select: linkProperties + }) + ]); + + return { items }; +}, ['wintersday-items'], { revalidate: 60 * 60 }); + + export default async function WintersdayPage() { + const { items } = await loadData(); await pageView('festival/wintersday'); return ( +

} id="items">
+ + } authorizationMessage={}> + {items.map((item) => ( + + + + + ))} + +
); } -export const metadata: Metadata = { - title: 'Wintersday' -}; +export async function generateMetadata({ params }: PageProps): Promise { + const { language } = await params; + const t = getTranslate(language); + + return { + title: { + absolute: `${t('festival.wintersday')} · gw2treasures.com` + } + }; +} diff --git a/apps/web/app/[language]/festival/wintersday/achievements/page.tsx b/apps/web/app/[language]/festival/wintersday/achievements/page.tsx index 3505ed8cd..7b8c1c736 100644 --- a/apps/web/app/[language]/festival/wintersday/achievements/page.tsx +++ b/apps/web/app/[language]/festival/wintersday/achievements/page.tsx @@ -13,6 +13,7 @@ import { Notice } from '@gw2treasures/ui/components/Notice/Notice'; import type { Metadata } from 'next'; import { requiredScopes } from '../helper'; import { pageView } from '@/lib/pageView'; +import { getTranslate } from '@/lib/translate'; const achievementIds = [ 5005, @@ -89,6 +90,12 @@ export default async function WintersdayAchievementsPage({ params }: PageProps) ); } -export const metadata: Metadata = { - title: 'Achievements' -}; +export async function generateMetadata({ params }: PageProps): Promise { + const { language } = await params; + const t = getTranslate(language); + + return { + title: t('navigation.achievements') + }; +} + diff --git a/apps/web/app/[language]/festival/wintersday/layout.tsx b/apps/web/app/[language]/festival/wintersday/layout.tsx index 103d005b3..dbeaa9f60 100644 --- a/apps/web/app/[language]/festival/wintersday/layout.tsx +++ b/apps/web/app/[language]/festival/wintersday/layout.tsx @@ -14,13 +14,14 @@ const endsAt = new Date('2025-01-02T17:00:00.000Z'); export default function WintersdayLayout({ children }: LayoutProps) { return ( - Time remaining: }>Wintersday)} skipLayout + Time remaining: }>)} skipLayout navBar={( }, { segment: 'achievements', label: }, { segment: 'skins', label: }, { segment: 'minis', label: }, + { segment: 'wizards-vault', label: }, ]}/> )} > diff --git a/apps/web/app/[language]/festival/wintersday/minis/page.tsx b/apps/web/app/[language]/festival/wintersday/minis/page.tsx index d0a50c890..ac3b508d2 100644 --- a/apps/web/app/[language]/festival/wintersday/minis/page.tsx +++ b/apps/web/app/[language]/festival/wintersday/minis/page.tsx @@ -8,6 +8,8 @@ import { db } from '@/lib/prisma'; import type { Metadata } from 'next'; import { requiredScopes } from '../helper'; import { pageView } from '@/lib/pageView'; +import { getTranslate } from '@/lib/translate'; +import type { PageProps } from '@/lib/next'; const miniIds = [ 115, // Mini Princess Doll @@ -76,6 +78,11 @@ export default async function WintersdayAchievementsPage() { ); } -export const metadata: Metadata = { - title: 'Minis' -}; +export async function generateMetadata({ params }: PageProps): Promise { + const { language } = await params; + const t = getTranslate(language); + + return { + title: t('festival.wintersday.minis') + }; +} diff --git a/apps/web/app/[language]/festival/wintersday/skins/page.tsx b/apps/web/app/[language]/festival/wintersday/skins/page.tsx index 776df9014..060c9841d 100644 --- a/apps/web/app/[language]/festival/wintersday/skins/page.tsx +++ b/apps/web/app/[language]/festival/wintersday/skins/page.tsx @@ -8,6 +8,8 @@ import { db } from '@/lib/prisma'; import type { Metadata } from 'next'; import { requiredScopes } from '../helper'; import { pageView } from '@/lib/pageView'; +import type { PageProps } from '@/lib/next'; +import { getTranslate } from '@/lib/translate'; const skinIds = [ // weapons @@ -103,6 +105,11 @@ export default async function WintersdayAchievementsPage() { ); } -export const metadata: Metadata = { - title: 'Skins' -}; +export async function generateMetadata({ params }: PageProps): Promise { + const { language } = await params; + const t = getTranslate(language); + + return { + title: t('navigation.skins') + }; +} diff --git a/apps/web/app/[language]/festival/wintersday/wizards-vault/page.tsx b/apps/web/app/[language]/festival/wintersday/wizards-vault/page.tsx new file mode 100644 index 000000000..86d8aac68 --- /dev/null +++ b/apps/web/app/[language]/festival/wintersday/wizards-vault/page.tsx @@ -0,0 +1,64 @@ +import { Gw2Accounts } from '@/components/Gw2Api/Gw2Accounts'; +import { Trans } from '@/components/I18n/Trans'; +import { PageLayout } from '@/components/Layout/PageLayout'; +import { cache } from '@/lib/cache'; +import { db } from '@/lib/prisma'; +import type { Metadata } from 'next'; +import { requiredScopes } from '../helper'; +import { pageView } from '@/lib/pageView'; +import Link from 'next/link'; +import { WizardsVaultObjective } from '@/components/WizardsVault/WizardsVaultObjective'; +import type { PageProps } from '@/lib/next'; +import { getTranslate } from '@/lib/translate'; +import { Icon } from '@gw2treasures/ui'; +import { Notice } from '@gw2treasures/ui/components/Notice/Notice'; + +const objectiveIds: number[] = [ + // TODO: add objective ids +]; + +const loadData = cache(async function loadData() { + const [objectives] = await Promise.all([ + db.wizardsVaultObjective.findMany({ + where: { OR: [{ id: { in: objectiveIds }}, { name_en: { startsWith: '(Festival)' }, removedFromApi: false }] }, + }) + ]); + + return { objectives }; +}, ['wintersday-wizards-vault-objectives'], { revalidate: 60 * 5 }); + + +export default async function WintersdayAchievementsPage({ params }: PageProps) { + const { language } = await params; + const { objectives } = await loadData(); + await pageView('festival/wintersday/wizwards-vault'); + + return ( + + } authorizationMessage={}/> + +

+ + {objectives.length > 0 ? objectives.map((objective) => ( + + )) : ( + No Wizard's Vault objectives for wintersday are available in the Guild Wars 2 API yet. + )} + +

+ {' '} + Visit the Wizard's Vault page to view all your active Wizard's Vault objectives. +

+ +
+ ); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { language } = await params; + const t = getTranslate(language); + + return { + title: t('navigation.wizardsVault') + }; +} diff --git a/apps/web/components/Gw2Api/Gw2AccountName.tsx b/apps/web/components/Gw2Api/Gw2AccountName.tsx index 0d97c18b8..fec7d2ab2 100644 --- a/apps/web/components/Gw2Api/Gw2AccountName.tsx +++ b/apps/web/components/Gw2Api/Gw2AccountName.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react'; import type { Gw2Account } from './types'; import { Tip } from '@gw2treasures/ui/components/Tip/Tip'; +import commonStyles from '@gw2treasures/ui/common.module.css'; interface Gw2AccountNameProps { account: Gw2Account; @@ -10,16 +11,16 @@ interface Gw2AccountNameProps { export const Gw2AccountName: FC = ({ account, long }) => { // if the account does not have a displayName, always just return the name if(!account.displayName) { - return account.name; + return {account.name}; } if(long) { - return `${account.displayName} (${account.name})`; + return {account.displayName} ({account.name}); } return ( - {account.displayName} + {account.displayName} ); }; diff --git a/apps/web/components/Gw2Api/Gw2Accounts.tsx b/apps/web/components/Gw2Api/Gw2Accounts.tsx index 813a4679a..23ccbc860 100644 --- a/apps/web/components/Gw2Api/Gw2Accounts.tsx +++ b/apps/web/components/Gw2Api/Gw2Accounts.tsx @@ -11,7 +11,7 @@ import { useUser } from '../User/use-user'; import { Gw2AccountLoginNotice } from './Gw2AccountLoginNotice'; export interface Gw2AccountsProps { - children?: (accounts: Gw2Account[], scopes: Scope[]) => ReactElement; + children?: ((accounts: Gw2Account[], scopes: Scope[]) => ReactElement) | ReactNode; requiredScopes: Scope[]; optionalScopes?: Scope[]; options?: GetAccountsOptions; @@ -58,5 +58,9 @@ const Gw2AccountsInternal: FC = ({ children, requiredScopes, o ); } - return children?.(accounts.accounts, accounts.scopes); + if(typeof children === 'function') { + return children?.(accounts.accounts, accounts.scopes); + } + + return children; }; diff --git a/apps/web/components/Item/ItemInventoryTable.tsx b/apps/web/components/Item/ItemInventoryTable.tsx index 04a0620ba..f60439096 100644 --- a/apps/web/components/Item/ItemInventoryTable.tsx +++ b/apps/web/components/Item/ItemInventoryTable.tsx @@ -39,7 +39,7 @@ export const ItemInventoryTable: FC = ({ itemId }) => { - Account + AccountLocationCount diff --git a/apps/web/components/WizardsVault/WizardsVaultObjective.module.css b/apps/web/components/WizardsVault/WizardsVaultObjective.module.css index 07a515b20..4e102aaac 100644 --- a/apps/web/components/WizardsVault/WizardsVaultObjective.module.css +++ b/apps/web/components/WizardsVault/WizardsVaultObjective.module.css @@ -1,3 +1,7 @@ +.wrapper + .wrapper { + margin-top: 64px; +} + .objective { background-color: var(--color-background-light); border: 1px solid var(--color-border-dark); diff --git a/apps/web/components/WizardsVault/WizardsVaultObjective.tsx b/apps/web/components/WizardsVault/WizardsVaultObjective.tsx index dae608445..fb03a3d21 100644 --- a/apps/web/components/WizardsVault/WizardsVaultObjective.tsx +++ b/apps/web/components/WizardsVault/WizardsVaultObjective.tsx @@ -13,7 +13,7 @@ interface WizardsVaultObjectiveProps { export const WizardsVaultObjective: FC = ({ objective, language }) => { return ( - <> +
{localizedName(objective, language)} @@ -23,6 +23,6 @@ export const WizardsVaultObjective: FC = ({ objectiv
- +
); }; diff --git a/apps/web/translations/de.json b/apps/web/translations/de.json index f5b671eaa..99b14797d 100644 --- a/apps/web/translations/de.json +++ b/apps/web/translations/de.json @@ -295,5 +295,13 @@ "fractal.uncategorized": "Nicht kategorisiert", "fractal.underground_facility": "Untergrundeinrichtung", "fractal.urban_battleground": "Urbanes Schlachtfeld", - "fractal.volcanic": "Vulkanisches Fraktal" + "fractal.volcanic": "Vulkanisches Fraktal", + "festival.wintersday": "Wintertag", + "festival.wintersday.intro": "Die Glocken klingen, Schneebälle fliegen und das fantastische Golem-Luftschiff von Spielzeugmacher Tixx macht Halt über Götterfels – in Tyria wird einmal mehr das traditionelle Wintertag-Fest gefeiert.", + "festival.wintersday.description": "Verfolge während des Wintertags deine Fortschritte auf allen deinen Konten auf gw2treasures.com.", + "festival.wintersday.achievements.description": "Diese Erfolge sind nur während des Wintertags verfügbar.", + "festival.wintersday.skins.description": "Diese Skins sind nur während des Wintertags verfügbar.", + "festival.wintersday.minis": "Miniaturen", + "festival.wintersday.minis.description": "Diese Miniaturen sind nur während des Wintertags verfügbar.", + "festival.wintersday.wizards-vault.description": "Diese Aufgaben im Gewölbe des Zauberers sind nur während dest Wintertags verfügbar." } diff --git a/apps/web/translations/en.json b/apps/web/translations/en.json index 76f6a21a8..8aa310d08 100644 --- a/apps/web/translations/en.json +++ b/apps/web/translations/en.json @@ -307,7 +307,10 @@ "fractal.urban_battleground": "Urban Battleground", "fractal.volcanic": "Volcanic", "festival.wintersday": "Wintersday", - "festival.wintersday.description": "Bells are ringing, snowballs are flying, and Toymaster Tixx’s fantastic golem-shaped airship is hovering over Divinity’s Reach—the traditional Tyrian celebration of Wintersday is here again!", + "festival.wintersday.intro": "Bells are ringing, snowballs are flying, and Toymaster Tixx’s fantastic golem-shaped airship is hovering over Divinity’s Reach—the traditional Tyrian celebration of Wintersday is here again!", + "festival.wintersday.description": "Keep track of your progress on all your accounts during Wintersday on gw2treasures.com.", + "festival.wintersday.items.authorize": "You need to authorize gw2treasures.com to be able to see your accounts inventories.", + "festival.wintersday.items.login": "You need to login to be able to see your accounts inventories.", "festival.wintersday.achievements.description": "These achievements are only available during wintersday.", "festival.wintersday.achievements.authorize": "You need to authorize gw2treasures.com to be able to see your accounts achievement progress.", "festival.wintersday.achievements.login": "You need to login to be able to see your accounts achievement progress.", @@ -317,5 +320,8 @@ "festival.wintersday.minis": "Minis", "festival.wintersday.minis.description": "These minis are only available during wintersday.", "festival.wintersday.minis.authorize": "You need to authorize gw2treasures.com to be able to see your accounts mini unlocks.", - "festival.wintersday.minis.login": "You need to login to be able to see your accounts mini unlocks." + "festival.wintersday.minis.login": "You need to login to be able to see your accounts mini unlocks.", + "festival.wintersday.wizards-vault.description": "These are all the Wizard's Vault objectives only active during Wintersday.", + "festival.wintersday.wizards-vault.authorize": "You need to authorize gw2treasures.com to be able to see your accounts Wizard's Vault progress.", + "festival.wintersday.wizards-vault.login": "You need to login to be able to see your accounts Wizard's Vault progress." } diff --git a/packages/ui/common.module.css b/packages/ui/common.module.css new file mode 100644 index 000000000..101ddcf4c --- /dev/null +++ b/packages/ui/common.module.css @@ -0,0 +1,3 @@ +.nowrap { + white-space: nowrap; +} diff --git a/packages/ui/index.ts b/packages/ui/index.ts index 9fb20bbf5..55f7f0760 100644 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -5,3 +5,6 @@ export * from './icons'; // lib export * from './lib'; + +import commonStyles from './common.module.css'; +export { commonStyles }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 744b271a4..63b68b93d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -12,6 +12,7 @@ "icons/", "lib/", "reset.module.css", + "common.module.css", "index.ts", "types.d.ts", "tsconfig.json", diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 0eecfe634..092f92dbf 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -4,6 +4,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", + "**/*.module.css", ".next/types/**/*.ts" ], }