"use client"; import { useTranslations } from "next-intl"; import { useEffect, useId, useMemo, useState } from "react"; import { getCatalogPreviewAssets } from "@/actions/catalog-preview-assets"; import { CurrencyIcon } from "@/components/shared/currency-icon"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { PackageOffer, PackageSnapshot, } from "@/features/catalog/domain/packages"; import { getLocalIconUrl } from "@/lib/furni/classname"; import { buildPackagePreview, createPreviewAssetLoader, parsePreviewItemIds, } from "./catalog-package-preview-model"; type Asset = { id: number; classname: string; name: string }; function PreviewIcon({ asset, missing }: { asset?: Asset; missing: string }) { const [failed, setFailed] = useState(false); if (!asset?.classname || failed) return ( {missing} ); return ( setFailed(true)} /> ); } function PreviewPrice({ offer }: { offer: PackageOffer }) { const t = useTranslations("pages.admin.catalogPackagePreview"); if (offer.costCredits < 0 || offer.costPoints < 0) return ( {t("specialPricing", { credits: offer.costCredits, points: offer.costPoints, type: offer.pointsType, })} ); return ( {offer.costCredits > 0 && ( {offer.costCredits} )} {offer.costPoints > 0 && ( {offer.pointsType === 0 || offer.pointsType === 5 ? ( ) : ( {t("pointsType", { type: offer.pointsType })} )} {offer.costPoints} )} {offer.costCredits === 0 && offer.costPoints === 0 && ( {t("free")} )} ); } function offerChanged(offer: PackageOffer, baseline?: PackageSnapshot) { if (!baseline) return false; const previous = baseline.offers.find((item) => item.id === offer.id); return ( !previous || (Object.keys(offer) as Array).some( (key) => offer[key] !== previous[key], ) ); } export function CatalogPackagePreview({ snapshot, baseline, }: { snapshot: PackageSnapshot; baseline?: PackageSnapshot; }) { const t = useTranslations("pages.admin.catalogPackagePreview"); const controlId = useId(); const [query, setQuery] = useState(""); const [profile, setProfile] = useState({ playerMode: true, rank: 1, club: false, vip: false, }); const [selectedPageId, setSelectedPageId] = useState(null); const [selectedOfferId, setSelectedOfferId] = useState(null); const [pagination, setPagination] = useState({ scope: "", index: 0 }); const preview = useMemo( () => buildPackagePreview(snapshot, profile, query), [snapshot, profile, query], ); const selectedPage = preview.pages.find((page) => page.id === selectedPageId) ?? preview.pages.find((page) => page.id === preview.matchingPageIds[0]); const normalized = query.trim().toLowerCase(); const offers = preview.offers.filter( (offer) => offer.pageId === selectedPage?.id && (!normalized || selectedPage.caption.toLowerCase().includes(normalized) || `${offer.catalogName} ${offer.id} ${offer.itemIds}` .toLowerCase() .includes(normalized)), ); const scope = `${selectedPage?.id}:${query}`; const lastIndex = Math.max(0, Math.ceil(offers.length / 48) - 1); const index = pagination.scope === scope ? Math.min(pagination.index, lastIndex) : 0; const visibleOffers = offers.slice(index * 48, (index + 1) * 48); const selectedOffer = offers.find((offer) => offer.id === selectedOfferId); const allItemIds = [ ...new Set( [...visibleOffers, ...(selectedOffer ? [selectedOffer] : [])].flatMap( (offer) => parsePreviewItemIds(offer.itemIds), ), ), ]; const assetKey = allItemIds .slice(0, 200) .sort((a, b) => a - b) .join(","); const loader = useMemo( () => createPreviewAssetLoader(getCatalogPreviewAssets), [], ); const [assetState, setAssetState] = useState<{ key: string; status: "ok" | "failed"; assets: Asset[]; }>({ key: "", status: "ok", assets: [] }); useEffect(() => { if (!assetKey) return; void loader.run( assetKey.split(",").map(Number), (result) => setAssetState({ key: assetKey, status: result.ok ? "ok" : "failed", assets: result.assets, }), () => setAssetState({ key: assetKey, status: "failed", assets: [] }), ); return () => loader.cancel(); }, [assetKey, loader]); const assets = new Map( (assetState.key === assetKey ? assetState.assets : []).map((asset) => [ asset.id, asset, ]), ); const genericLayout = selectedPage && [ "default_3x3", "default_3x3_color_grouping", "default_3x3_pet", "default_3x3_wallpaper", "default_3x3_floor", "default_3x3_landscape", ].includes(selectedPage.pageLayout); return (

{t("hint")}

{preview.externalParents && (

{t("externalParents")}

)} {preview.invalidCount > 0 && (

{t("invalidHierarchy", { count: preview.invalidCount })}

)} {preview.truncated && (

{t("snapshotLimit")}

)} {preview.restrictedCount > 0 && (

{t("restricted", { count: preview.restrictedCount })}

)}
{selectedPage && ( <>

{selectedPage.caption}

{t("offerCount", { count: offers.length })}

{t("layout", { layout: selectedPage.pageLayout || t("missingLayout"), })}

{!genericLayout && (

{t("unsupportedLayout")}

)} {assetKey && assetState.key !== assetKey && (

{t("loadingIcons")}

)} {assetState.key === assetKey && assetState.status === "failed" && (

{t("assetsUnavailable")}

)} {allItemIds.length > 200 && (

{t("assetLimit")}

)} {(preview.restrictedOfferCounts[selectedPage.id] ?? 0) > 0 && (

{t("restrictedOffers", { count: preview.restrictedOfferCounts[selectedPage.id], })}

)} {offers.length === 0 ? (

{query ? t("noMatches") : (preview.restrictedOfferCounts[selectedPage.id] ?? 0) > 0 ? t("restrictedOffers", { count: preview.restrictedOfferCounts[selectedPage.id], }) : t("emptyPage")}

) : (
{visibleOffers.map((offer) => { const ids = parsePreviewItemIds(offer.itemIds); const asset = assets.get(ids[0]); return ( ); })}
)} {lastIndex > 0 && (
{t("page", { page: index + 1, total: lastIndex + 1 })}
)} )} {selectedOffer ? (

{selectedOffer.catalogName || `#${selectedOffer.id}`}

{t("offerId", { id: selectedOffer.id })} ·{" "} {t("amount", { count: selectedOffer.amount })}

    {parsePreviewItemIds(selectedOffer.itemIds).map((id) => (
  • {assets.get(id)?.name || t("unknownFurniture")} · #{id}
  • ))}
{offerChanged(selectedOffer, baseline) && (

{t("changed")}

)}
) : ( selectedPage && (

{t("selectOffer")}

) )}
); }