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