fix : correct color
This commit is contained in:
@@ -17,7 +17,7 @@ import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui
|
|||||||
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
|
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type Character = { key: string; name: string; aliases: string[]; element: string | null; rarity: number | null; imageUrl: string };
|
type Character = { key: string; name: string; aliases: string[]; element: string | null; rarity: number | null; imageUrl: string };
|
||||||
@@ -85,7 +85,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
|
|||||||
const disabled = existing.has(character.key);
|
const disabled = existing.has(character.key);
|
||||||
return (
|
return (
|
||||||
<Card key={character.key} className="overflow-hidden py-0">
|
<Card key={character.key} className="overflow-hidden py-0">
|
||||||
<div className={cn("relative aspect-square", rarityGradientClass(character.rarity))}><Image src={character.imageUrl} alt={character.name} fill className="object-cover" /></div>
|
<div className={cn("relative aspect-square", characterRarityGradientClass(character.key, character.rarity))}><Image src={character.imageUrl} alt={character.name} fill className="object-cover" /></div>
|
||||||
<CardHeader className="pt-4">
|
<CardHeader className="pt-4">
|
||||||
<CardTitle>{character.name}</CardTitle>
|
<CardTitle>{character.name}</CardTitle>
|
||||||
<CardDescription>{character.element ?? "Unknown element"}</CardDescription>
|
<CardDescription>{character.element ?? "Unknown element"}</CardDescription>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, Pagi
|
|||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import type { GlossaryKind } from "@/lib/glossary";
|
import type { GlossaryKind } from "@/lib/glossary";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type CatalogItem = { key: string; name: string; imageUrl: string; rarity?: number | null };
|
type CatalogItem = { key: string; name: string; imageUrl: string; rarity?: number | null };
|
||||||
@@ -113,6 +113,7 @@ export function GlossaryManager({
|
|||||||
onValueChange={(key) => setItem(catalogs[kind].find((value) => value.key === key) ?? null)}
|
onValueChange={(key) => setItem(catalogs[kind].find((value) => value.key === key) ?? null)}
|
||||||
placeholder="ค้นหารายการ…"
|
placeholder="ค้นหารายการ…"
|
||||||
rarityBackground
|
rarityBackground
|
||||||
|
characterBackground={kind === "character"}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
@@ -154,7 +155,7 @@ export function GlossaryManager({
|
|||||||
{catalogItem ? (
|
{catalogItem ? (
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"relative size-10 shrink-0 overflow-hidden rounded-md bg-muted",
|
"relative size-10 shrink-0 overflow-hidden rounded-md bg-muted",
|
||||||
rarityGradientClass(catalogItem.rarity),
|
entry.kind === "character" ? characterRarityGradientClass(catalogItem.key, catalogItem.rarity) : rarityGradientClass(catalogItem.rarity),
|
||||||
)}>
|
)}>
|
||||||
<Image src={catalogItem.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
<Image src={catalogItem.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ import {
|
|||||||
} from "@/lib/guides/stats";
|
} from "@/lib/guides/stats";
|
||||||
import { elementBackgroundColor, elementIconFilter } from "@/lib/catalog/element";
|
import { elementBackgroundColor, elementIconFilter } from "@/lib/catalog/element";
|
||||||
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
||||||
import {
|
import {
|
||||||
applyPresetFields,
|
applyPresetFields,
|
||||||
@@ -3911,7 +3911,7 @@ function TeamEditor({
|
|||||||
return (
|
return (
|
||||||
<div key={member.characterKey} className="min-w-0 text-center">
|
<div key={member.characterKey} className="min-w-0 text-center">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className={cn("relative size-16 shrink-0 overflow-hidden rounded-lg bg-muted", rarityGradientClass(isCustomTravelerKey(member.characterKey) ? 5 : character?.rarity))}>
|
<div className={cn("relative size-16 shrink-0 overflow-hidden rounded-lg bg-muted", characterRarityGradientClass(member.characterKey, isCustomTravelerKey(member.characterKey) ? 5 : character?.rarity))}>
|
||||||
{character ? <Image src={character.imageUrl} alt={character.name} fill sizes="64px" className="object-contain" /> : null}
|
{character ? <Image src={character.imageUrl} alt={character.name} fill sizes="64px" className="object-contain" /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@@ -4162,6 +4162,7 @@ function ExtraCatalogItemsEditor({
|
|||||||
placeholder={`เลือก${labels[kind]}`}
|
placeholder={`เลือก${labels[kind]}`}
|
||||||
showSelectedImage
|
showSelectedImage
|
||||||
rarityBackground
|
rarityBackground
|
||||||
|
characterBackground={kind === "character"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ function ItemPicker({
|
|||||||
placeholder={`Search ${kind}…`}
|
placeholder={`Search ${kind}…`}
|
||||||
emptyLabel="No matches"
|
emptyLabel="No matches"
|
||||||
rarityBackground
|
rarityBackground
|
||||||
|
characterBackground={kind === "character"}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
ComboboxTrigger,
|
ComboboxTrigger,
|
||||||
FuzzyCombobox,
|
FuzzyCombobox,
|
||||||
} from "@/components/ui/combobox";
|
} from "@/components/ui/combobox";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface AdminCatalogItem {
|
export interface AdminCatalogItem {
|
||||||
@@ -50,6 +50,7 @@ type CommonSelectProps<Item extends AdminCatalogItem> = {
|
|||||||
emptyLabel?: string;
|
emptyLabel?: string;
|
||||||
invalid?: boolean;
|
invalid?: boolean;
|
||||||
rarityBackground?: boolean;
|
rarityBackground?: boolean;
|
||||||
|
characterBackground?: boolean;
|
||||||
searchNames?: (item: Item) => string[];
|
searchNames?: (item: Item) => string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
|||||||
emptyLabel = "ไม่พบรายการ",
|
emptyLabel = "ไม่พบรายการ",
|
||||||
invalid = false,
|
invalid = false,
|
||||||
rarityBackground = false,
|
rarityBackground = false,
|
||||||
|
characterBackground = false,
|
||||||
showSelectedImage = false,
|
showSelectedImage = false,
|
||||||
showClear = true,
|
showClear = true,
|
||||||
label,
|
label,
|
||||||
@@ -82,7 +84,7 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative size-10 shrink-0 overflow-hidden rounded-md",
|
"relative size-10 shrink-0 overflow-hidden rounded-md",
|
||||||
rarityBackground && rarityGradientClass(selected.rarity),
|
rarityBackground && (characterBackground ? characterRarityGradientClass(selected.key, selected.rarity) : rarityGradientClass(selected.rarity)),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Image src={selected.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
<Image src={selected.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
||||||
@@ -110,7 +112,7 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
|||||||
<ComboboxList>
|
<ComboboxList>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<ComboboxItem key={item.key} value={item}>
|
<ComboboxItem key={item.key} value={item}>
|
||||||
<CatalogOptionImage item={item} rarityBackground={rarityBackground} />
|
<CatalogOptionImage item={item} rarityBackground={rarityBackground} characterBackground={characterBackground} />
|
||||||
<span className="whitespace-nowrap">{item.name}</span>
|
<span className="whitespace-nowrap">{item.name}</span>
|
||||||
</ComboboxItem>
|
</ComboboxItem>
|
||||||
)}
|
)}
|
||||||
@@ -138,6 +140,7 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
|||||||
emptyLabel = "ไม่พบรายการ",
|
emptyLabel = "ไม่พบรายการ",
|
||||||
invalid = false,
|
invalid = false,
|
||||||
rarityBackground = true,
|
rarityBackground = true,
|
||||||
|
characterBackground = false,
|
||||||
searchNames = (item) => [item.name, ...(item.aliases ?? [])],
|
searchNames = (item) => [item.name, ...(item.aliases ?? [])],
|
||||||
className,
|
className,
|
||||||
}: CatalogSlotSelectProps<Item>) {
|
}: CatalogSlotSelectProps<Item>) {
|
||||||
@@ -158,7 +161,7 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
|||||||
aria-invalid={invalid}
|
aria-invalid={invalid}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md border bg-muted p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&>svg]:hidden",
|
"relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md border bg-muted p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&>svg]:hidden",
|
||||||
rarityBackground && selected && rarityGradientClass(selected.rarity),
|
rarityBackground && selected && (characterBackground ? characterRarityGradientClass(selected.key, selected.rarity) : rarityGradientClass(selected.rarity)),
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -174,7 +177,7 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
|||||||
<ComboboxList>
|
<ComboboxList>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<ComboboxItem key={item.key} value={item}>
|
<ComboboxItem key={item.key} value={item}>
|
||||||
<CatalogOptionImage item={item} rarityBackground={rarityBackground} />
|
<CatalogOptionImage item={item} rarityBackground={rarityBackground} characterBackground={characterBackground} />
|
||||||
<span className="whitespace-nowrap">{item.name}</span>
|
<span className="whitespace-nowrap">{item.name}</span>
|
||||||
</ComboboxItem>
|
</ComboboxItem>
|
||||||
)}
|
)}
|
||||||
@@ -216,7 +219,7 @@ function DomainCatalogSelect<Item extends AdminCatalogItem>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CharacterSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
export function CharacterSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
||||||
return <DomainCatalogSelect {...props} />;
|
return <DomainCatalogSelect {...props} characterBackground />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WeaponSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
export function WeaponSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
||||||
@@ -318,15 +321,17 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
|
|||||||
function CatalogOptionImage({
|
function CatalogOptionImage({
|
||||||
item,
|
item,
|
||||||
rarityBackground,
|
rarityBackground,
|
||||||
|
characterBackground = false,
|
||||||
}: {
|
}: {
|
||||||
item: AdminCatalogItem;
|
item: AdminCatalogItem;
|
||||||
rarityBackground: boolean;
|
rarityBackground: boolean;
|
||||||
|
characterBackground?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative size-8 shrink-0 overflow-hidden rounded-md",
|
"relative size-8 shrink-0 overflow-hidden rounded-md",
|
||||||
rarityBackground && rarityGradientClass(item.rarity),
|
rarityBackground && (characterBackground ? characterRarityGradientClass(item.key, item.rarity) : rarityGradientClass(item.rarity)),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Image src={item.imageUrl} alt="" fill sizes="32px" className="object-contain" />
|
<Image src={item.imageUrl} alt="" fill sizes="32px" className="object-contain" />
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import Image from "next/image";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import type { CommissionRequest } from "@/lib/commission/request";
|
import type { CommissionRequest } from "@/lib/commission/request";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type Item = { key: string; name: string; imageUrl: string; rarity?: number | null; element?: string | null };
|
type Item = { key: string; name: string; imageUrl: string; rarity?: number | null; element?: string | null };
|
||||||
type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
|
type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
|
||||||
|
|
||||||
function ItemImage({ item, size = "small" }: { item: Item; size?: "small" | "large" }) {
|
function ItemImage({ item, size = "small", character = false }: { item: Item; size?: "small" | "large"; character?: boolean }) {
|
||||||
return <div title={item.name} className={cn("relative aspect-square min-w-0 overflow-hidden rounded-lg", rarityGradientClass(item.rarity))}>
|
return <div title={item.name} className={cn("relative aspect-square min-w-0 overflow-hidden rounded-lg", character ? characterRarityGradientClass(item.key, item.rarity) : rarityGradientClass(item.rarity))}>
|
||||||
<Image src={item.imageUrl} alt={item.name} fill sizes={size === "large" ? "160px" : "64px"} className="object-contain" />
|
<Image src={item.imageUrl} alt={item.name} fill sizes={size === "large" ? "160px" : "64px"} className="object-contain" />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ function CommissionTeamCard({ team, index, catalog }: { team: CommissionRequest[
|
|||||||
const artifact = artifacts.get(member.artifactKey);
|
const artifact = artifacts.get(member.artifactKey);
|
||||||
return <div key={memberIndex} className="min-w-0 rounded-lg p-1 lg:p-1.5" style={{ backgroundColor: elementBackgroundColor(character?.element) }}>
|
return <div key={memberIndex} className="min-w-0 rounded-lg p-1 lg:p-1.5" style={{ backgroundColor: elementBackgroundColor(character?.element) }}>
|
||||||
<div className="grid grid-cols-1 gap-1 lg:grid-cols-[2fr_1fr]">
|
<div className="grid grid-cols-1 gap-1 lg:grid-cols-[2fr_1fr]">
|
||||||
{character ? <ItemImage item={character} size="large" /> : <div className="aspect-square rounded-lg bg-muted" />}
|
{character ? <ItemImage item={character} size="large" character /> : <div className="aspect-square rounded-lg bg-muted" />}
|
||||||
<div className="grid min-w-0 grid-cols-2 gap-0.5 lg:grid-cols-1 lg:grid-rows-2 lg:gap-1">
|
<div className="grid min-w-0 grid-cols-2 gap-0.5 lg:grid-cols-1 lg:grid-rows-2 lg:gap-1">
|
||||||
{weapon ? <ItemImage item={weapon} /> : <div title={member.weaponKey} className="aspect-square rounded-lg bg-muted" />}
|
{weapon ? <ItemImage item={weapon} /> : <div title={member.weaponKey} className="aspect-square rounded-lg bg-muted" />}
|
||||||
{artifact ? <ItemImage item={artifact} /> : <div title={member.artifactKey} className="aspect-square rounded-lg bg-muted" />}
|
{artifact ? <ItemImage item={artifact} /> : <div title={member.artifactKey} className="aspect-square rounded-lg bg-muted" />}
|
||||||
@@ -77,7 +77,7 @@ export function CommissionRequestSummary({ request, catalog, amountBaht }: {
|
|||||||
: <p key={key} className="w-16 wrap-break-word text-xs">{key}</p>;
|
: <p key={key} className="w-16 wrap-break-word text-xs">{key}</p>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" character /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>;
|
</Card>;
|
||||||
})}
|
})}
|
||||||
@@ -89,7 +89,7 @@ export function CommissionRequestSummary({ request, catalog, amountBaht }: {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid grid-cols-[minmax(0,1fr)_64px] items-center gap-3">
|
<CardContent className="grid grid-cols-[minmax(0,1fr)_64px] items-center gap-3">
|
||||||
<div className="flex min-w-0 flex-wrap gap-2">{set.levels.map((level) => <Badge key={level} variant="outline">C{level}</Badge>)}</div>
|
<div className="flex min-w-0 flex-wrap gap-2">{set.levels.map((level) => <Badge key={level} variant="outline">C{level}</Badge>)}</div>
|
||||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" character /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>;
|
</Card>;
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -776,8 +776,24 @@ export function CommissionTicketChat({
|
|||||||
mine ? "items-end" : "items-start",
|
mine ? "items-end" : "items-start",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-full px-1 text-center text-xs font-medium text-muted-foreground",
|
||||||
|
mine ? "translate-x-5" : "-translate-x-5",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<time
|
||||||
|
dateTime={sentAt.toISOString()}
|
||||||
|
title={sentAt.toLocaleString("th-TH")}
|
||||||
|
>
|
||||||
|
{sentAt.toLocaleTimeString("th-TH", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
<MessageHeader className="px-1">
|
<MessageHeader className="px-1">
|
||||||
{mine ? "คุณ" : message.authorName}
|
{message.authorName}
|
||||||
</MessageHeader>
|
</MessageHeader>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -947,22 +963,6 @@ export function CommissionTicketChat({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<MessageFooter
|
|
||||||
className={cn(
|
|
||||||
"w-full justify-center gap-1 px-1 group-data-[align=end]/message:justify-center",
|
|
||||||
mine ? "translate-x-5" : "-translate-x-5",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<time
|
|
||||||
dateTime={sentAt.toISOString()}
|
|
||||||
title={sentAt.toLocaleString("th-TH")}
|
|
||||||
>
|
|
||||||
{sentAt.toLocaleTimeString("th-TH", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</time>
|
|
||||||
</MessageFooter>
|
|
||||||
</MessageContent>
|
</MessageContent>
|
||||||
</Message>
|
</Message>
|
||||||
);
|
);
|
||||||
@@ -970,7 +970,6 @@ export function CommissionTicketChat({
|
|||||||
{visibleOptimisticMessages.map((message) => (
|
{visibleOptimisticMessages.map((message) => (
|
||||||
<Message key={message.localId} align="end">
|
<Message key={message.localId} align="end">
|
||||||
<MessageContent className="w-full items-end gap-1">
|
<MessageContent className="w-full items-end gap-1">
|
||||||
<MessageHeader className="px-1">คุณ</MessageHeader>
|
|
||||||
<Bubble
|
<Bubble
|
||||||
variant={message.imageUrl ? "ghost" : "muted"}
|
variant={message.imageUrl ? "ghost" : "muted"}
|
||||||
className="max-w-[min(82%,36rem)]"
|
className="max-w-[min(82%,36rem)]"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function CommissionTicketDetail({ id, admin }: { id: string; admin:
|
|||||||
const catalog = await getCommissionLabels(data.checkout.request);
|
const catalog = await getCommissionLabels(data.checkout.request);
|
||||||
return <div className="flex min-h-0 flex-1 flex-col gap-3">
|
return <div className="flex min-h-0 flex-1 flex-col gap-3">
|
||||||
<CommissionTicketPanels summary={
|
<CommissionTicketPanels summary={
|
||||||
<Card className="h-full min-h-0"><CardHeader className="shrink-0"><CardTitle>คำขอที่ชำระเงินแล้ว - ฿{data.checkout.amountBaht}</CardTitle></CardHeader>
|
<Card className="h-full min-h-0"><CardHeader className="shrink-0"><CardTitle>รายละเอียดคำขอ - ฿{data.checkout.amountBaht}</CardTitle></CardHeader>
|
||||||
<CardContent className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto">{catalog && <CommissionRequestSummary request={data.checkout.request} catalog={catalog} amountBaht={data.checkout.amountBaht} />}
|
<CardContent className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto">{catalog && <CommissionRequestSummary request={data.checkout.request} catalog={catalog} amountBaht={data.checkout.amountBaht} />}
|
||||||
<p className="text-sm text-muted-foreground">การชำระเงิน: {data.payment.transRef} - {data.payment.transferredAt.toLocaleString("th-TH")}</p>
|
<p className="text-sm text-muted-foreground">การชำระเงิน: {data.payment.transRef} - {data.payment.transferredAt.toLocaleString("th-TH")}</p>
|
||||||
<CommissionSlipDialog src={publicMediaUrl(data.payment.slipObjectKey)} />
|
<CommissionSlipDialog src={publicMediaUrl(data.payment.slipObjectKey)} />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
StygianBossTab,
|
StygianBossTab,
|
||||||
} from "@/components/stygian/stygian-boss-display";
|
} from "@/components/stygian/stygian-boss-display";
|
||||||
import { elementIconFilter } from "@/lib/catalog/element";
|
import { elementIconFilter } from "@/lib/catalog/element";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import type { getActiveCatalog } from "@/lib/guides/queries";
|
import type { getActiveCatalog } from "@/lib/guides/queries";
|
||||||
import type { getStygianSchedule } from "@/lib/stygian/repository";
|
import type { getStygianSchedule } from "@/lib/stygian/repository";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -47,7 +47,7 @@ function RecommendationIcon({ item, catalog, teamLayout = false }: { item: Item;
|
|||||||
return (
|
return (
|
||||||
<div className={cn("flex min-w-0 flex-col gap-1 rounded-lg border bg-card p-1", teamLayout && "w-full sm:p-2", teamLayout && hasEquipment && "sm:w-[164px]", teamLayout && !hasEquipment && "sm:w-[116px]", teamLayout && !hasEquipment && item.constellationLevel === null && "aspect-square justify-center")}>
|
<div className={cn("flex min-w-0 flex-col gap-1 rounded-lg border bg-card p-1", teamLayout && "w-full sm:p-2", teamLayout && hasEquipment && "sm:w-[164px]", teamLayout && !hasEquipment && "sm:w-[116px]", teamLayout && !hasEquipment && item.constellationLevel === null && "aspect-square justify-center")}>
|
||||||
<div className={teamLayout && hasEquipment ? "flex flex-col gap-1 sm:grid sm:grid-cols-[100px_40px] sm:gap-2" : "flex flex-col gap-1"}>
|
<div className={teamLayout && hasEquipment ? "flex flex-col gap-1 sm:grid sm:grid-cols-[100px_40px] sm:gap-2" : "flex flex-col gap-1"}>
|
||||||
<div className={cn("relative aspect-square overflow-hidden rounded-lg", teamLayout && "sm:size-[100px]", teamLayout && hasEquipment && "sm:row-span-2", rarityGradientClass(value.rarity))}>
|
<div className={cn("relative aspect-square overflow-hidden rounded-lg", teamLayout && "sm:size-[100px]", teamLayout && hasEquipment && "sm:row-span-2", characterRarityGradientClass(item.catalogKey, value.rarity))}>
|
||||||
{value.imageUrl ? <Image src={value.imageUrl} alt={value.name} fill sizes={teamLayout ? "(min-width: 640px) 128px, 25vw" : "128px"} className="object-contain" /> : null}
|
{value.imageUrl ? <Image src={value.imageUrl} alt={value.name} fill sizes={teamLayout ? "(min-width: 640px) 128px, 25vw" : "128px"} className="object-contain" /> : null}
|
||||||
</div>
|
</div>
|
||||||
{hasEquipment ? <div className={teamLayout ? "grid grid-cols-2 gap-1 sm:row-span-2 sm:grid-cols-1 sm:gap-2 sm:self-center" : "grid grid-cols-2 gap-1"}>
|
{hasEquipment ? <div className={teamLayout ? "grid grid-cols-2 gap-1 sm:row-span-2 sm:grid-cols-1 sm:gap-2 sm:self-center" : "grid grid-cols-2 gap-1"}>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||||
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
||||||
import type { PublicGuide } from "@/lib/guides/queries";
|
import type { PublicGuide } from "@/lib/guides/queries";
|
||||||
import { teamConstellationLabel, teamRefinementLabel } from "@/lib/guides/team-tools";
|
import { teamConstellationLabel, teamRefinementLabel } from "@/lib/guides/team-tools";
|
||||||
@@ -226,7 +226,7 @@ export function TeamExportImage({ team }: { team: TeamExportData }) {
|
|||||||
const percent = total > 0 ? damage / total * 100 : 0;
|
const percent = total > 0 ? damage / total * 100 : 0;
|
||||||
return (
|
return (
|
||||||
<div key={member.position} className="flex h-43.5 items-center gap-6 rounded-3xl p-3.5" style={{ backgroundColor: elementBackgroundColor(member.character.element) }}>
|
<div key={member.position} className="flex h-43.5 items-center gap-6 rounded-3xl p-3.5" style={{ backgroundColor: elementBackgroundColor(member.character.element) }}>
|
||||||
<div className={cn("relative size-35 shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20", rarityGradientClass(isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity))}>
|
<div className={cn("relative size-35 shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20", characterRarityGradientClass(member.character.key, isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity))}>
|
||||||
<Image src={member.character.imageUrl} alt="" fill loading="eager" sizes="140px" className="object-contain" />
|
<Image src={member.character.imageUrl} alt="" fill loading="eager" sizes="140px" className="object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid w-44 shrink-0 grid-cols-2 gap-3">
|
<div className="grid w-44 shrink-0 grid-cols-2 gap-3">
|
||||||
@@ -316,7 +316,8 @@ function TeamCard({
|
|||||||
exporting
|
exporting
|
||||||
? "col-span-2 row-span-2"
|
? "col-span-2 row-span-2"
|
||||||
: "sm:col-span-2 sm:row-span-2",
|
: "sm:col-span-2 sm:row-span-2",
|
||||||
rarityGradientClass(
|
characterRarityGradientClass(
|
||||||
|
member.character.key,
|
||||||
isCustomTravelerKey(member.character.key)
|
isCustomTravelerKey(member.character.key)
|
||||||
? 5
|
? 5
|
||||||
: member.character.rarity,
|
: member.character.rarity,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { rarityGradient, rarityGradientClass, rarityTextClass, rarityTextColor } from "@/lib/catalog/rarity";
|
import { characterRarityGradientClass, rarityGradient, rarityGradientClass, rarityTextClass, rarityTextColor } from "@/lib/catalog/rarity";
|
||||||
|
|
||||||
describe("catalog rarity backgrounds", () => {
|
describe("catalog rarity backgrounds", () => {
|
||||||
it("maps every weapon and artifact rarity to its Tailwind gradient", () => {
|
it("maps every weapon and artifact rarity to its Tailwind gradient", () => {
|
||||||
expect(rarityGradientClass(1)).toContain("rgba(23,25,28,1)");
|
expect(rarityGradientClass(1)).toBe("bg-[#697177] bg-[linear-gradient(90deg,rgba(105,113,119,1)_0%,rgba(137,144,147,1)_100%)]");
|
||||||
expect(rarityGradientClass(2)).toContain("rgba(11,43,24,1)");
|
expect(rarityGradientClass(2)).toBe("bg-[#5D8972] bg-[linear-gradient(90deg,rgba(93,137,114,1)_0%,rgba(121,167,127,1)_100%)]");
|
||||||
|
expect(rarityGradient(1)).toBe("linear-gradient(90deg, rgba(105, 113, 119, 1) 0%, rgba(137, 144, 147, 1) 100%)");
|
||||||
|
expect(rarityGradient(2)).toBe("linear-gradient(90deg, rgba(93, 137, 114, 1) 0%, rgba(121, 167, 127, 1) 100%)");
|
||||||
expect(rarityGradientClass(3)).toBe("bg-[#4D7691] bg-[linear-gradient(90deg,rgba(77,118,145,1)_0%,rgba(71,135,165,1)_100%)]");
|
expect(rarityGradientClass(3)).toBe("bg-[#4D7691] bg-[linear-gradient(90deg,rgba(77,118,145,1)_0%,rgba(71,135,165,1)_100%)]");
|
||||||
expect(rarityGradientClass(4)).toBe("bg-[#6D6A9C] bg-[linear-gradient(90deg,rgba(109,106,156,1)_0%,rgba(150,122,179,1)_100%)]");
|
expect(rarityGradientClass(4)).toBe("bg-[#6D6A9C] bg-[linear-gradient(90deg,rgba(109,106,156,1)_0%,rgba(150,122,179,1)_100%)]");
|
||||||
expect(rarityGradientClass(5)).toBe("bg-[#A67B34] bg-[linear-gradient(90deg,rgba(166,123,52,1)_0%,rgba(178,123,44,1)_100%)]");
|
expect(rarityGradientClass(5)).toBe("bg-[#A67B34] bg-[linear-gradient(90deg,rgba(166,123,52,1)_0%,rgba(178,123,44,1)_100%)]");
|
||||||
@@ -26,4 +28,14 @@ describe("catalog rarity backgrounds", () => {
|
|||||||
expect(rarityTextClass(undefined)).toBeUndefined();
|
expect(rarityTextClass(undefined)).toBeUndefined();
|
||||||
expect(rarityTextColor(undefined)).toBe("#17171c");
|
expect(rarityTextColor(undefined)).toBe("#17171c");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses red for special characters without changing other rarity backgrounds", () => {
|
||||||
|
for (const key of ["10000062", "10000117", "10000118"]) {
|
||||||
|
expect(characterRarityGradientClass(key, 5)).toBe("bg-[#9A424E] bg-[linear-gradient(90deg,#9A424E_0%,#B85D67_100%)]");
|
||||||
|
expect(characterRarityGradientClass(key, null)).toBe(characterRarityGradientClass(key, 5));
|
||||||
|
}
|
||||||
|
expect(characterRarityGradientClass("ordinary-character", 5)).toBe(rarityGradientClass(5));
|
||||||
|
expect(characterRarityGradientClass("ordinary-character", 1)).toBe(rarityGradientClass(1));
|
||||||
|
expect(characterRarityGradientClass("ordinary-character", 2)).toBe(rarityGradientClass(2));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-4
@@ -1,14 +1,17 @@
|
|||||||
const rarityGradientClasses: Record<number, string> = {
|
const rarityGradientClasses: Record<number, string> = {
|
||||||
1: "bg-[linear-gradient(90deg,rgba(23,25,28,1)_0%,rgba(19,20,23,1)_100%)]",
|
1: "bg-[#697177] bg-[linear-gradient(90deg,rgba(105,113,119,1)_0%,rgba(137,144,147,1)_100%)]",
|
||||||
2: "bg-[linear-gradient(90deg,rgba(11,43,24,1)_0%,rgba(7,18,13,1)_100%)]",
|
2: "bg-[#5D8972] bg-[linear-gradient(90deg,rgba(93,137,114,1)_0%,rgba(121,167,127,1)_100%)]",
|
||||||
3: "bg-[#4D7691] bg-[linear-gradient(90deg,rgba(77,118,145,1)_0%,rgba(71,135,165,1)_100%)]",
|
3: "bg-[#4D7691] bg-[linear-gradient(90deg,rgba(77,118,145,1)_0%,rgba(71,135,165,1)_100%)]",
|
||||||
4: "bg-[#6D6A9C] bg-[linear-gradient(90deg,rgba(109,106,156,1)_0%,rgba(150,122,179,1)_100%)]",
|
4: "bg-[#6D6A9C] bg-[linear-gradient(90deg,rgba(109,106,156,1)_0%,rgba(150,122,179,1)_100%)]",
|
||||||
5: "bg-[#A67B34] bg-[linear-gradient(90deg,rgba(166,123,52,1)_0%,rgba(178,123,44,1)_100%)]",
|
5: "bg-[#A67B34] bg-[linear-gradient(90deg,rgba(166,123,52,1)_0%,rgba(178,123,44,1)_100%)]",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const specialCharacterKeys = new Set(["10000062", "10000117", "10000118"]);
|
||||||
|
const specialCharacterGradientClass = "bg-[#9A424E] bg-[linear-gradient(90deg,#9A424E_0%,#B85D67_100%)]";
|
||||||
|
|
||||||
const rarityGradients: Record<number, string> = {
|
const rarityGradients: Record<number, string> = {
|
||||||
1: "linear-gradient(90deg, rgba(23, 25, 28, 1) 0%, rgba(19, 20, 23, 1) 100%)",
|
1: "linear-gradient(90deg, rgba(105, 113, 119, 1) 0%, rgba(137, 144, 147, 1) 100%)",
|
||||||
2: "linear-gradient(90deg, rgba(11, 43, 24, 1) 0%, rgba(7, 18, 13, 1) 100%)",
|
2: "linear-gradient(90deg, rgba(93, 137, 114, 1) 0%, rgba(121, 167, 127, 1) 100%)",
|
||||||
3: "linear-gradient(90deg, rgba(77, 118, 145, 1) 0%, rgba(71, 135, 165, 1) 100%)",
|
3: "linear-gradient(90deg, rgba(77, 118, 145, 1) 0%, rgba(71, 135, 165, 1) 100%)",
|
||||||
4: "linear-gradient(90deg, rgba(109, 106, 156, 1) 0%, rgba(150, 122, 179, 1) 100%)",
|
4: "linear-gradient(90deg, rgba(109, 106, 156, 1) 0%, rgba(150, 122, 179, 1) 100%)",
|
||||||
5: "linear-gradient(90deg, rgba(166, 123, 52, 1) 0%, rgba(178, 123, 44, 1) 100%)",
|
5: "linear-gradient(90deg, rgba(166, 123, 52, 1) 0%, rgba(178, 123, 44, 1) 100%)",
|
||||||
@@ -30,6 +33,10 @@ export function rarityGradientClass(rarity: number | null | undefined): string |
|
|||||||
return rarity ? rarityGradientClasses[rarity] : undefined;
|
return rarity ? rarityGradientClasses[rarity] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function characterRarityGradientClass(key: string | null | undefined, rarity: number | null | undefined): string | undefined {
|
||||||
|
return key && specialCharacterKeys.has(key) ? specialCharacterGradientClass : rarityGradientClass(rarity);
|
||||||
|
}
|
||||||
|
|
||||||
export function rarityGradient(rarity: number | null | undefined): string {
|
export function rarityGradient(rarity: number | null | undefined): string {
|
||||||
return (rarity && rarityGradients[rarity]) || "#17383d";
|
return (rarity && rarityGradients[rarity]) || "#17383d";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ function rarity(value: string | undefined): number | null {
|
|||||||
QUALITY_BLUE: 3,
|
QUALITY_BLUE: 3,
|
||||||
QUALITY_PURPLE: 4,
|
QUALITY_PURPLE: 4,
|
||||||
QUALITY_ORANGE: 5,
|
QUALITY_ORANGE: 5,
|
||||||
|
QUALITY_ORANGE_SP: 5,
|
||||||
}[value] ?? null
|
}[value] ?? null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
|
|
||||||
export const lunarisVersionSchema = z.string().regex(/^\d+(?:\.\d+)+$/u);
|
export const lunarisVersionSchema = z.string().regex(/^\d+(?:\.\d+)+$/u);
|
||||||
export const catalogFormatVersion = 2;
|
export const catalogFormatVersion = 3;
|
||||||
|
|
||||||
export interface LunarisVersionChange {
|
export interface LunarisVersionChange {
|
||||||
previousVersion: string;
|
previousVersion: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user