mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
consumable prices
This commit is contained in:
1 parent
885c260605
commit
d2b4cc3e7d
4 files changed
+113
-62
No files matched your search
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"price": {
|
||||
"free": "Free"
|
||||
},
|
||||
|
||||
"nations": {
|
||||
"0": "Unspecified",
|
||||
"1": "USSR",
|
||||
|
||||
@@ -6,23 +6,35 @@ import { useLocale } from "../../hooks/useLocale";
|
||||
import { classNames } from "../../ui/classNames";
|
||||
import { Text } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
|
||||
interface PriceProps extends ComponentProps<"div"> {
|
||||
price: StandardPrice;
|
||||
price?: StandardPrice;
|
||||
}
|
||||
|
||||
export function Price({ price, className, ...props }: PriceProps) {
|
||||
const strings = useStrings();
|
||||
const normalized =
|
||||
price === undefined
|
||||
? []
|
||||
: price.price_list.filter(
|
||||
(entry) =>
|
||||
entry.currency_price === undefined ||
|
||||
entry.currency_price.amount !== 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.price, className)} {...props}>
|
||||
{price.price_list.map((entry, index) => (
|
||||
{normalized.map((entry, index) => (
|
||||
<PriceEntry key={index} entry={entry} />
|
||||
))}
|
||||
|
||||
{price.price_list.length === 0 && (
|
||||
<Text className={styles.entry} lowContrast>
|
||||
Free
|
||||
</Text>
|
||||
)}
|
||||
{price === undefined ||
|
||||
(normalized.length === 0 && (
|
||||
<Text className={styles.entry} lowContrast>
|
||||
{strings.price.free}
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,25 +18,32 @@
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--space-3);
|
||||
|
||||
.consumable {
|
||||
font-size: var(--space-5);
|
||||
.consumable-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
|
||||
img {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
.consumable {
|
||||
font-size: var(--space-5);
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
img {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.entry {
|
||||
.info {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-direction: column;
|
||||
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
.entry {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import { TankopediaCollapsible } from "../TankopediaCollapsible";
|
||||
import type { ConsumableComponent } from "@protos/blitz_static_consumable_component";
|
||||
import { Tank } from "@protos/tank";
|
||||
import { select } from "three/src/nodes/math/ConditionalNode.js";
|
||||
import type { BlitzStaticPurchaseComponent } from "@protos/blitz_static_purchase_component";
|
||||
|
||||
interface TankopediaLoadoutProps {
|
||||
characteristics: ComputedCharacteristics;
|
||||
@@ -95,7 +96,13 @@ function Consumables({ characteristics }: ConsumablesProps) {
|
||||
{Object.entries(consumables.consumables).map(
|
||||
([id, { compatibility, consumable, purchase }]) => {
|
||||
if (!isCompatible(compatibility!)) return null;
|
||||
return <Consumable id={id} consumable={consumable!} />;
|
||||
return (
|
||||
<Consumable
|
||||
id={id}
|
||||
consumable={consumable!}
|
||||
purchase={purchase!}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
@@ -106,51 +113,72 @@ function Consumables({ characteristics }: ConsumablesProps) {
|
||||
interface ConsumableProps {
|
||||
id: string;
|
||||
consumable: ConsumableComponent;
|
||||
purchase: BlitzStaticPurchaseComponent;
|
||||
}
|
||||
|
||||
function Consumable({ id, consumable }: ConsumableProps) {
|
||||
function Consumable({ id, consumable, purchase }: ConsumableProps) {
|
||||
const gameStrings = useGameStrings("ConsumableEntity");
|
||||
const isSelected = Tankopedia.use((state) =>
|
||||
state.protagonist.consumables.includes(id),
|
||||
);
|
||||
const tank = useProtagonist();
|
||||
const tierPrices = useTierPrices();
|
||||
|
||||
const price = useMemo(() => {
|
||||
const { prices } = tierPrices.prices[purchase.price_per_tier_catalog_id];
|
||||
|
||||
for (const { tier_catalog_id, unlock_price } of prices) {
|
||||
if (tier_catalog_id === tank.tank!.tier_catalog_id) {
|
||||
return unlock_price;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`No price found for tier catalog id ${tank.tank!.tier_catalog_id}`,
|
||||
);
|
||||
}, [tank.id, purchase.price_per_tier_catalog_id]);
|
||||
|
||||
return (
|
||||
<Tooltip tooltip={gameStrings[consumable!.name_key]}>
|
||||
<Button
|
||||
radius="1"
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
color={isSelected ? undefined : "gray"}
|
||||
className={styles.consumable}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
const index = draft.protagonist.consumables.indexOf(id);
|
||||
<div className={styles["consumable-wrapper"]}>
|
||||
<Tooltip tooltip={gameStrings[consumable!.name_key]}>
|
||||
<Button
|
||||
radius="1"
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
color={isSelected ? undefined : "gray"}
|
||||
className={styles.consumable}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
const index = draft.protagonist.consumables.indexOf(id);
|
||||
|
||||
if (index === -1) {
|
||||
draft.protagonist.consumables.push(id);
|
||||
} else {
|
||||
draft.protagonist.consumables.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={`/media/consumables/${id}.webp`} />
|
||||
<div className={styles.info}>
|
||||
<Text lowContrast size="minor">
|
||||
<div className={styles.entry}>
|
||||
<ClockIcon />
|
||||
10s
|
||||
</div>
|
||||
</Text>
|
||||
if (index === -1) {
|
||||
draft.protagonist.consumables.push(id);
|
||||
} else {
|
||||
draft.protagonist.consumables.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={`/media/consumables/${id}.webp`} />
|
||||
<div className={styles.info}>
|
||||
<Text lowContrast size="minor">
|
||||
<div className={styles.entry}>
|
||||
<ClockIcon />
|
||||
10s
|
||||
</div>
|
||||
</Text>
|
||||
|
||||
<Text lowContrast size="minor">
|
||||
<div className={styles.entry}>
|
||||
<ResetIcon />
|
||||
10s
|
||||
</div>
|
||||
</Text>
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Text lowContrast size="minor">
|
||||
<div className={styles.entry}>
|
||||
<ResetIcon />
|
||||
10s
|
||||
</div>
|
||||
</Text>
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Price price={price} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -484,8 +512,8 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip tooltip={gameStrings[stage.display_name]}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.wrapper}>
|
||||
<Tooltip tooltip={gameStrings[stage.display_name]}>
|
||||
<Button
|
||||
className={styles.stage}
|
||||
color={isSelected ? undefined : "gray"}
|
||||
@@ -569,9 +597,9 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
: romanize(stage.number)}
|
||||
</Text>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{<Price className={styles.price} price={price} />}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{<Price className={styles.price} price={price} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user