feat : improve UX/UI

This commit is contained in:
2026-10-05 20:01:41 +07:00 Unverified
parent 2f1fe0ab76
commit 3c3b104779
9 changed files with 168 additions and 182 deletions
+2 -2
View File
@@ -20,9 +20,9 @@ export default function HomePage() {
<GoogleAdsense />
<LiveRefresh endpoint="/api/events/directory" />
<SiteHeader />
<main className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 py-10 sm:px-6 sm:py-14">
<main className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 pt-4 pb-10 sm:px-6 sm:pt-6 sm:pb-14">
<header className="max-w-3xl">
<h1 className="mt-2 font-heading text-4xl font-semibold">
<h1 className="font-heading text-4xl font-semibold">
เลือกตัวละครแล้วส่องดูได้เลย
</h1>
<p className="mt-3 text-lg leading-8 text-muted-foreground">
+19 -22
View File
@@ -13,6 +13,7 @@ import {
CopyIcon,
GripVerticalIcon,
HeartPulseIcon,
MousePointerClickIcon,
PlusIcon,
RefreshCcwIcon,
SearchIcon,
@@ -130,6 +131,7 @@ import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Toggle } from "@/components/ui/toggle";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Dialog,
DialogContent,
@@ -918,17 +920,17 @@ function SortableConditionTab({
<div
ref={setNodeRef}
style={{ transform: CSS.Transform.toString(transform), transition }}
className={cn("flex shrink-0 items-center rounded-md border", isDragging && "z-10 opacity-60")}
className={cn("flex shrink-0 items-center rounded-md", isDragging && "z-10 opacity-60")}
>
<ToggleGroupItem
<TabsTrigger
value={item.clientId}
className="border-0 aria-pressed:bg-primary aria-pressed:text-primary-foreground"
onClick={onSelect}
onDoubleClick={onRename}
>
<MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />
{label}
{index === 0 ? <Badge variant={active ? "secondary" : "outline"}>Default</Badge> : null}
</ToggleGroupItem>
</TabsTrigger>
<SimpleTooltip text={`ลากเพื่อจัดลำดับ ${label}`}>
<Button
type="button"
@@ -980,15 +982,11 @@ function SortableConditionSwitcher({
}}
>
<SortableContext items={items.map((item) => item.clientId)} strategy={rectSortingStrategy}>
<ToggleGroup
aria-label={ariaLabel}
variant="outline"
value={activeId ? [activeId] : []}
onValueChange={(values) => {
const index = items.findIndex((item) => item.clientId === values[0]);
<Tabs value={activeId} onValueChange={(value) => {
const index = items.findIndex((item) => item.clientId === value);
if (index !== -1) onActiveIndexChange(index);
}}
>
}}>
<TabsList aria-label={ariaLabel} className="shrink-0 gap-1">
{items.map((item, index) => (
<SortableConditionTab
key={item.clientId}
@@ -999,7 +997,8 @@ function SortableConditionSwitcher({
onRename={() => onRename(index)}
/>
))}
</ToggleGroup>
</TabsList>
</Tabs>
</SortableContext>
</DndContext>
</ScrollableNav>
@@ -3812,18 +3811,16 @@ function TeamEditor({
</Button>
{activeImportGuide.groups.length > 1 ? (
<ScrollableNav ariaLabel="เลือกเงื่อนไขทีม" className="w-full min-w-0">
<ToggleGroup
aria-label="เลือกเงื่อนไขทีม"
variant="outline"
value={[guideImportCondition]}
onValueChange={(values) => { if (values[0]) setGuideImportCondition(values[0]); }}
>
<Tabs value={guideImportCondition} onValueChange={setGuideImportCondition}>
<TabsList aria-label="เลือกเงื่อนไขทีม">
{activeImportGuide.groups.map((group, index) => (
<ToggleGroupItem key={group.id} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">
<TabsTrigger key={group.id} value={String(index)}>
<MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />
{group.name || `Condition ${index + 1}`}
</ToggleGroupItem>
</TabsTrigger>
))}
</ToggleGroup>
</TabsList>
</Tabs>
</ScrollableNav>
) : null}
<div className="flex min-w-0 items-center gap-2">
+2 -28
View File
@@ -49,14 +49,6 @@ import {
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Spinner } from "@/components/ui/spinner";
import { useAdminPresence } from "@/hooks/use-presence";
import type { CatalogSyncJob } from "@/lib/catalog/sync";
@@ -86,7 +78,6 @@ export function StructuredDashboard({
const router = useRouter();
const [pending, startTransition] = useTransition();
const [query, setQuery] = useState("");
const [sort, setSort] = useState("default");
const [syncJob, setSyncJob] = useState<CatalogSyncJob | null>(null);
const presence = useAdminPresence(null);
const catalogReady = Boolean(catalogVersion);
@@ -95,10 +86,7 @@ export function StructuredDashboard({
const search = searchItems(guides, query,
(guide) => `${guide.name} ${guide.characterKey} ${guide.overview}`,
(guide) => [guide.name]);
const filteredGuides = sort === "default" ? search.items : [...search.items].sort((left, right) => {
const order = left.name.localeCompare(right.name, "th", { numeric: true });
return sort === "name-asc" ? order : -order;
});
const filteredGuides = search.items;
useEffect(() => {
if (!syncJobId || !syncJobStatus || ["completed", "unchanged", "cancelled", "error"].includes(syncJobStatus)) return;
@@ -198,8 +186,7 @@ export function StructuredDashboard({
</Card>
) : null}
{guides.length ? (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<InputGroup className="h-11 max-w-2xl flex-1">
<InputGroup className="h-11 max-w-2xl">
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
@@ -210,19 +197,6 @@ export function StructuredDashboard({
aria-label="ค้นหาตัวละคร"
/>
</InputGroup>
<Select value={sort} onValueChange={(value) => value && setSort(value)}>
<SelectTrigger aria-label="เรียง Guide" className="h-11 w-full sm:w-44">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="default">เรียงตามเดิม</SelectItem>
<SelectItem value="name-asc">ชื่อ ก-ฮ</SelectItem>
<SelectItem value="name-desc">ชื่อ ฮ-ก</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
) : null}
{filteredGuides.length ? (
+21 -33
View File
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { DownloadIcon } from "lucide-react";
import { DownloadIcon, MousePointerClickIcon } from "lucide-react";
import { GuideImageExport } from "@/components/public/guide-image-export";
import { Button } from "@/components/ui/button";
@@ -15,7 +15,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
const sectionOptions = [
["tierlist", "อันดับอาร์ติแฟกต์"],
@@ -106,53 +106,41 @@ export function ArtifactImageExport({
{sections.has("best-stats") && recommendedConditionLabels.length > 1 ? (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">เงื่อนไขค่าสถานะแนะนำ</p>
<ToggleGroup
variant="outline"
value={[recommendedCondition]}
onValueChange={(values) => {
if (values[0]) setRecommendedCondition(values[0]);
}}
>
<Tabs value={recommendedCondition} onValueChange={setRecommendedCondition}>
<TabsList aria-label="เงื่อนไขค่าสถานะแนะนำ">
{recommendedConditionLabels.map((label, index) => (
<ToggleGroupItem
key={index}
value={String(index)}
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
>
<TabsTrigger key={index} value={String(index)}>
<MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />
{label}
</ToggleGroupItem>
</TabsTrigger>
))}
</ToggleGroup>
</TabsList>
</Tabs>
</div>
) : null}
{sections.has("tierlist") && tierConditionLabels.length > 1 ? (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">เงื่อนไขอันดับอาร์ติแฟกต์</p>
<ToggleGroup variant="outline" value={[tierCondition]} onValueChange={(values) => { if (values[0]) setTierCondition(values[0]); }}>
{tierConditionLabels.map((label, index) => <ToggleGroupItem key={index} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">{label}</ToggleGroupItem>)}
</ToggleGroup>
<Tabs value={tierCondition} onValueChange={setTierCondition}>
<TabsList aria-label="เงื่อนไขอันดับอาร์ติแฟกต์">
{tierConditionLabels.map((label, index) => <TabsTrigger key={index} value={String(index)}><MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />{label}</TabsTrigger>)}
</TabsList>
</Tabs>
</div>
) : null}
{sections.has("target-stats") && targetConditionLabels.length > 1 ? (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">เงื่อนไขค่าสถานะเป้าหมาย</p>
<ToggleGroup
variant="outline"
value={[targetCondition]}
onValueChange={(values) => {
if (values[0]) setTargetCondition(values[0]);
}}
>
<Tabs value={targetCondition} onValueChange={setTargetCondition}>
<TabsList aria-label="เงื่อนไขค่าสถานะเป้าหมาย">
{targetConditionLabels.map((label, index) => (
<ToggleGroupItem
key={index}
value={String(index)}
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
>
<TabsTrigger key={index} value={String(index)}>
<MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />
{label}
</ToggleGroupItem>
</TabsTrigger>
))}
</ToggleGroup>
</TabsList>
</Tabs>
</div>
) : null}
</>
+10 -11
View File
@@ -1,9 +1,10 @@
"use client";
import { MousePointerClickIcon } from "lucide-react";
import { Children, type ReactNode, useState } from "react";
import { ScrollableNav } from "@/components/public/scrollable-nav";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export function ConditionSwitcher({
children,
@@ -20,23 +21,21 @@ export function ConditionSwitcher({
if (items.length <= 1) return items[0] ?? null;
return (
<div className="flex min-w-0 flex-col gap-4">
<Tabs className="min-w-0 gap-4" value={active} onValueChange={setActive}>
<ScrollableNav ariaLabel={label} className="w-full min-w-0">
<ToggleGroup aria-label={label} variant="outline" value={[active]} onValueChange={(values) => {
if (values[0]) setActive(values[0]);
}}>
<TabsList aria-label={label} className="mx-auto shrink-0">
{items.map((_, index) => (
<ToggleGroupItem
<TabsTrigger
key={index}
value={String(index)}
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
>
<MousePointerClickIcon className="text-primary group-aria-pressed/tabs:text-primary-foreground" aria-hidden="true" data-icon="inline-start" />
{labels?.[index] || `Condition ${index + 1}`}
</ToggleGroupItem>
</TabsTrigger>
))}
</ToggleGroup>
</TabsList>
</ScrollableNav>
{items[Number(active)]}
</div>
{items.map((item, index) => <TabsContent key={index} value={String(index)} className="text-base">{item}</TabsContent>)}
</Tabs>
);
}
+61 -35
View File
@@ -5,8 +5,10 @@ import { searchItems } from "@/lib/search";
import Image from "next/image";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { SearchIcon, UsersIcon } from "lucide-react";
import { useMemo } from "react";
import { SearchIcon, StarIcon, UsersIcon } from "lucide-react";
import { useMemo, type ReactNode } from "react";
import { ElementIcon } from "@/components/public/element-icon";
import {
Card,
@@ -28,6 +30,7 @@ import {
InputGroupInput,
} from "@/components/ui/input-group";
import { useVisitorPresence } from "@/hooks/use-presence";
import { weaponTypeIconPath } from "@/lib/overview-image";
import {
Select,
SelectContent,
@@ -36,6 +39,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type GuideCard = {
id: string;
@@ -46,6 +50,7 @@ type GuideCard = {
version: number;
aliases: string[];
element: string | null;
elementIconUrl: string | null;
weaponType: string | null;
rarity: number | null;
updatedAt: Date;
@@ -70,6 +75,13 @@ const weaponTypeLabels: Record<string, string> = {
WEAPON_CATALYST: "สื่อเวท",
};
const sortOptions = [
{ value: "manual", label: "แนะนำ" },
{ value: "recent", label: "อัปเดตล่าสุด" },
{ value: "name-asc", label: "A - Z" },
{ value: "name-desc", label: "Z - A" },
] as const;
export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
const searchParams = useSearchParams();
const query = searchParams.get("q") ?? "";
@@ -77,17 +89,16 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
const elements = useMemo(() => [...new Set(guides.flatMap((guide) => guide.element ? [guide.element] : []))].sort(), [guides]);
const weapons = useMemo(() => [...new Set(guides.flatMap((guide) => guide.weaponType ? [guide.weaponType] : []))].sort(), [guides]);
const rarities = useMemo(() => [...new Set(guides.flatMap((guide) => guide.rarity ? [guide.rarity] : []))].sort((a, b) => b - a), [guides]);
const elementParam = searchParams.get("element");
const weaponParam = searchParams.get("weapon");
const rarityParam = searchParams.get("rarity");
const element = elementParam && elements.includes(elementParam) ? elementParam : "all";
const weapon = weaponParam && weapons.includes(weaponParam) ? weaponParam : "all";
const rarity = rarityParam && rarities.includes(Number(rarityParam)) ? rarityParam : "all";
const sort = searchParams.get("sort") === "recent" ? "recent" : "manual";
const elementIconUrls = useMemo(() => Object.fromEntries(guides.flatMap((guide) => guide.element && guide.elementIconUrl ? [[guide.element, guide.elementIconUrl]] : [])), [guides]);
const selectedElements = [...new Set((searchParams.get("element") ?? "").split(",").filter((value) => elements.includes(value)))];
const selectedWeapons = [...new Set((searchParams.get("weapon") ?? "").split(",").filter((value) => weapons.includes(value)))];
const selectedRarities = [...new Set((searchParams.get("rarity") ?? "").split(",").filter((value) => rarities.includes(Number(value))))];
const sortParam = searchParams.get("sort");
const sort = sortParam === "recent" || sortParam === "name-asc" || sortParam === "name-desc" ? sortParam : "manual";
const matchingGuides = guides.filter((guide) =>
(element === "all" || guide.element === element) &&
(weapon === "all" || guide.weaponType === weapon) &&
(rarity === "all" || guide.rarity === Number(rarity)),
(!selectedElements.length || (guide.element !== null && selectedElements.includes(guide.element))) &&
(!selectedWeapons.length || (guide.weaponType !== null && selectedWeapons.includes(guide.weaponType))) &&
(!selectedRarities.length || (guide.rarity !== null && selectedRarities.includes(String(guide.rarity)))),
);
const search = searchItems(matchingGuides, query,
(guide) => `${guide.name} ${guide.overview} ${guide.aliases.join(" ")}`,
@@ -95,6 +106,11 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
const filtered = sort === "recent"
? [...search.items].sort((left, right) =>
new Date(right.publishedAt ?? right.updatedAt).getTime() - new Date(left.publishedAt ?? left.updatedAt).getTime())
: sort === "name-asc" || sort === "name-desc"
? [...search.items].sort((left, right) => {
const order = left.name.localeCompare(right.name, "th", { numeric: true });
return sort === "name-asc" ? order : -order;
})
: search.items;
function setParam(name: string, value: string) {
@@ -106,8 +122,9 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
}
return (
<div className="flex flex-col gap-8">
<div className="flex flex-wrap items-center gap-2">
<InputGroup className="h-11 min-w-64 flex-1">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<InputGroup className="h-9 w-48 shrink-0">
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
@@ -118,11 +135,22 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
aria-label="ค้นหาไกด์"
/>
</InputGroup>
<div className="flex flex-wrap gap-2" aria-label="ตัวกรองไกด์">
<GuideFilter label="ธาตุ" value={element} options={elements} optionLabels={elementLabels} onChange={(value) => setParam("element", value)} />
<GuideFilter label="อาวุธ" value={weapon} options={weapons} optionLabels={weaponTypeLabels} onChange={(value) => setParam("weapon", value)} />
<GuideFilter label="ดาว" value={rarity} options={rarities.map(String)} onChange={(value) => setParam("rarity", value)} />
<GuideFilter label="เรียงตาม" value={sort} options={["manual", "recent"]} optionLabels={{ manual: "แนะนำ", recent: "อัปเดตล่าสุด" }} includeAll={false} onChange={(value) => setParam("sort", value)} />
<Select value={sort} onValueChange={(next) => next && setParam("sort", next)}>
<SelectTrigger aria-label="เรียงตาม"><SelectValue>{sortOptions.find((option) => option.value === sort)?.label}</SelectValue></SelectTrigger>
<SelectContent>
<SelectGroup>
{sortOptions.map((option) => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="flex min-w-0 items-center gap-2 overflow-x-auto pb-1" aria-label="ตัวกรองไกด์">
<GuideToggleFilter label="ธาตุ" value={selectedElements} options={elements} optionLabels={elementLabels} optionIcon={(option) => elementIconUrls[option] ? <ElementIcon element={option} url={elementIconUrls[option]} /> : null} onChange={(values) => setParam("element", values.join(","))} />
<GuideToggleFilter label="อาวุธ" value={selectedWeapons} options={weapons} optionLabels={weaponTypeLabels} optionIcon={(option) => {
const src = weaponTypeIconPath(option);
return src ? <Image src={src} alt="" width={16} height={16} className="size-4 object-contain" /> : null;
}} onChange={(values) => setParam("weapon", values.join(","))} />
<GuideToggleFilter label="ดาว" value={selectedRarities} options={rarities.map(String)} optionIcon={() => <StarIcon className="size-4 fill-current text-yellow-500" />} optionText={(option) => option} onChange={(values) => setParam("rarity", values.join(","))} />
</div>
</div>
@@ -176,34 +204,32 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
);
}
function GuideFilter({
function GuideToggleFilter({
label,
value,
options,
optionLabels = {},
includeAll = true,
optionIcon,
optionText,
onChange,
}: {
label: string;
value: string;
value: string[];
options: string[];
optionLabels?: Record<string, string>;
includeAll?: boolean;
onChange: (value: string) => void;
optionIcon?: (value: string) => ReactNode;
optionText?: (value: string) => string;
onChange: (value: string[]) => void;
}) {
return (
<Select value={value} onValueChange={(next) => next && onChange(next)}>
<SelectTrigger aria-label={label}>
<SelectValue>{optionLabels[value] ?? (value === "all" ? label : value)}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{includeAll ? <SelectItem value="all">{label}: ทั้งหมด</SelectItem> : null}
<div className="shrink-0 border-l pl-2">
<ToggleGroup aria-label={label} variant="outline" size="sm" value={value} onValueChange={onChange}>
{options.map((option) => (
<SelectItem key={option} value={option}>{optionLabels[option] ?? option}</SelectItem>
<ToggleGroupItem key={option} value={option} aria-label={`${label}: ${optionLabels[option] ?? option}`} title={`${label}: ${optionLabels[option] ?? option}`} className="aria-pressed:border-primary aria-pressed:bg-transparent aria-pressed:text-primary">
{optionIcon?.(option)}{optionText?.(option)}
</ToggleGroupItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</ToggleGroup>
</div>
);
}
-1
View File
@@ -209,7 +209,6 @@ function GuideOverview({
return (
<div className="flex flex-col gap-8">
<div className="mx-auto flex max-w-3xl flex-col gap-3 text-center">
<p className="text-sm font-medium text-primary">ภาพรวมแนวทาง</p>
<h1 className="font-heading text-4xl font-bold">{guide.name}</h1>
<p className="text-lg font-semibold">{guide.overview}</p>
<div className="flex flex-wrap justify-center gap-2">
+7 -6
View File
@@ -3,7 +3,7 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react";
import { ArrowUpRightIcon, MousePointerClickIcon, SearchIcon, XIcon } from "lucide-react";
import { useMemo, useState, type ReactNode } from "react";
import { GuideImageExport } from "@/components/public/guide-image-export";
@@ -24,6 +24,7 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { elementBackgroundColor } from "@/lib/catalog/element";
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
@@ -448,11 +449,11 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
{groups.length > 1 ? (
<ScrollableNav ariaLabel="เลือกเงื่อนไขทีม" className="w-full min-w-0">
<ToggleGroup aria-label="เลือกเงื่อนไขทีม" variant="outline" value={[activeCondition]} onValueChange={(values) => {
if (values[0]) setActiveCondition(values[0]);
}}>
{groups.map((group, index) => <ToggleGroupItem key={group.id} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">{group.name || `เงื่อนไข ${index + 1}`}</ToggleGroupItem>)}
</ToggleGroup>
<Tabs className="mx-auto" value={activeCondition} onValueChange={setActiveCondition}>
<TabsList aria-label="เลือกเงื่อนไขทีม">
{groups.map((group, index) => <TabsTrigger key={group.id} value={String(index)}><MousePointerClickIcon aria-hidden="true" data-icon="inline-start" />{group.name || `เงื่อนไข ${index + 1}`}</TabsTrigger>)}
</TabsList>
</Tabs>
</ScrollableNav>
) : null}
{activeGroup?.note ? <p className="whitespace-pre-wrap text-muted-foreground">{activeGroup.note}</p> : null}
+3 -1
View File
@@ -265,6 +265,7 @@ export async function listPublicGuideCards() {
updatedAt: guides.updatedAt,
publishedAt: guides.publishedAt,
element: catalogCharacters.element,
elementIconKey: catalogCharacters.elementIconKey,
weaponType: catalogCharacters.weaponType,
rarity: catalogCharacters.rarity,
coverObjectKey: media.objectKey,
@@ -279,10 +280,11 @@ export async function listPublicGuideCards() {
alias: glossaryAliases.alias,
}).from(glossaryAliases)]);
const grouped = aliasesByItem(aliases);
return rows.map(({ coverObjectKey, ...guide }) => ({
return rows.map(({ coverObjectKey, elementIconKey, ...guide }) => ({
...guide,
aliases: grouped.character.get(guide.characterKey) ?? [],
coverUrl: publicMediaUrl(coverObjectKey),
elementIconUrl: elementIconKey ? publicMediaUrl(elementIconKey) : null,
}));
}