Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,6 @@
},
"store": {
"already_subscribed": "Already subscribed.",
"buy_for": "Buy for {price}",
"change_tier_failed": "Couldn't update your subscription. Please try again.",
"change_tier_success": "Switched to {tier}.",
"checkout_failed": "Failed to create checkout session.",
Expand All @@ -1319,7 +1318,7 @@
"confirm_purchase_title": "Confirm Purchase",
"confirm_upgrade": "Upgrade to {tier}? You'll be charged the prorated difference now.",
"currency_pack_purchase_success": "Currency pack purchase successful!",
"custom_amount": "Custom Amount",
"custom_amount": "Choose Amount",
"custom_currency_purchase_success": "Plutonium purchase successful!",
"effects": "Effects",
"flags": "Flags",
Expand All @@ -1332,6 +1331,7 @@
"no_subscriptions": "No subscriptions available. Check back later for new items.",
"packs": "Packs",
"patterns": "Skins",
"plutonium_amount": "Plutonium amount",
"price_per_month": "/mo",
"purchase_currency": "Purchase {currency}",
"purchase_failed": "Purchase failed. Please try again.",
Expand Down
77 changes: 33 additions & 44 deletions src/client/components/CustomCurrencyCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { createCustomCurrencyCheckout } from "../Api";
import { translateText } from "../Utils";
import "./CosmeticContainer";
import "./PlutoniumIcon";
import "./PurchaseButton";

// Fixed rate: 20 plutonium = $1.00 (5 cents each). Bounds and rate are
// enforced server-side; these are for UX only.
Expand All @@ -13,7 +15,6 @@ const MAX_PLUTONIUM = 2000;
export class CustomCurrencyCard extends LitElement {
/** Always a clamped integer in [MIN_PLUTONIUM, MAX_PLUTONIUM]. */
@state() private amount = 100;
@state() private busy = false;

createRenderRoot() {
return this;
Expand All @@ -36,73 +37,61 @@ export class CustomCurrencyCard extends LitElement {
this.amount = this.clamp(Number((e.target as HTMLInputElement).value));
}

private async buy() {
if (this.busy) return;
this.busy = true;
try {
const url = await createCustomCurrencyCheckout(this.amount);
if (url === false) {
alert(translateText("store.checkout_failed"));
return;
}
window.location.href = url;
} finally {
this.busy = false;
private buy = async () => {
const url = await createCustomCurrencyCheckout(this.amount);
if (url === false) {
alert(translateText("store.checkout_failed"));
return;
}
}
window.location.href = url;
};

render() {
const price = `$${this.priceDollars}`;
return html`
<div
class="relative flex flex-col items-center justify-between gap-2 p-3 w-48 rounded-xl border border-white/15 backdrop-blur-md"
style="background: linear-gradient(to top, rgba(80,80,80,0.55) 0%, rgba(15,15,20,0.85) 100%);"
<cosmetic-container
class="flex flex-col items-center justify-between gap-2 p-3 w-48 h-full"
.rarity=${"common"}
.name=${translateText("store.custom_amount")}
>
<div
class="text-xs font-bold uppercase tracking-wider text-center text-white/70 w-full"
class="relative flex flex-col items-center justify-center gap-1 w-full aspect-square bg-white/5 rounded-lg p-2 border border-white/10 overflow-hidden"
>
${translateText("store.custom_amount")}
</div>

<div class="flex flex-col items-center gap-2 w-full">
<plutonium-icon .size=${64}></plutonium-icon>
<span class="text-lg font-black text-green-400"
>${this.amount.toLocaleString()}</span
>
<span class="text-[10px] font-bold text-white/50 uppercase"
>${translateText("cosmetics.hard")}</span
<label for="custom-plutonium-amount" class="sr-only"
>${translateText("store.plutonium_amount")}</label
>

<input
type="range"
class="w-full accent-green-500 cursor-pointer"
id="custom-plutonium-amount"
type="number"
class="custom-plutonium-input w-24 text-center bg-black/30 border border-green-500/30 rounded px-1 py-0.5 text-lg font-black text-green-400 outline-none focus:border-green-400 focus:ring-1 focus:ring-green-400/40"
aria-label=${translateText("store.plutonium_amount")}
min=${MIN_PLUTONIUM}
max=${MAX_PLUTONIUM}
step="1"
.value=${String(this.amount)}
@input=${this.onSlider}
@change=${this.onInputChange}
/>

<span class="text-[10px] font-bold text-white/50 uppercase"
>${translateText("cosmetics.hard")}</span
>
<input
type="number"
class="w-24 text-center bg-black/30 border border-white/20 rounded px-2 py-1 text-white text-sm"
aria-label=${translateText("store.custom_amount")}
type="range"
class="w-[90%] accent-green-500 cursor-pointer"
aria-label=${translateText("store.plutonium_amount")}
min=${MIN_PLUTONIUM}
max=${MAX_PLUTONIUM}
step="1"
.value=${String(this.amount)}
@change=${this.onInputChange}
@input=${this.onSlider}
/>
</div>

<button
class="w-full mt-1 px-2 py-1.5 bg-green-500/20 text-green-400 border border-green-500/30 rounded-lg text-base font-bold cursor-pointer transition-all duration-200 hover:bg-green-500 hover:border-green-400 hover:text-white hover:shadow-[0_0_20px_rgba(74,222,128,0.6)] disabled:opacity-50 disabled:cursor-not-allowed"
?disabled=${this.busy}
@click=${this.buy}
>
${translateText("store.buy_for", { price })}
</button>
</div>
<purchase-button
.dollarPrice=${price}
.onPurchaseDollar=${this.buy}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
ryanbarlow97 marked this conversation as resolved.
></purchase-button>
Comment thread
ryanbarlow97 marked this conversation as resolved.
</cosmetic-container>
`;
}
}
12 changes: 10 additions & 2 deletions src/client/components/PurchaseButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ export class PurchaseButton extends LitElement {
@property({ type: Object })
product: Product | null = null;

/** Price shown for a direct dollar checkout without a catalog product. */
@property({ type: String })
dollarPrice: string = "";

@property({ type: Number })
priceHard: number | null = null;

Expand Down Expand Up @@ -263,6 +267,9 @@ export class PurchaseButton extends LitElement {
}

private renderDollarButton() {
const price = this.dollarPrice || this.product?.price;
if (!price) return nothing;

return html`
<button
class="purchase-sparkle-btn relative overflow-hidden w-full px-2 py-1.5 bg-green-500/20 text-green-400 border border-green-500/30 rounded-lg text-base font-bold cursor-pointer transition-all duration-200
Expand All @@ -272,7 +279,7 @@ export class PurchaseButton extends LitElement {
<span class="purchase-sparkle-streak"></span>
${this.dollarLabelKey
? html`${translateText(this.dollarLabelKey)} `
: nothing}${this.product!.price}${this.priceSuffix}
: nothing}${price}${this.priceSuffix}
</button>
`;
}
Expand Down Expand Up @@ -310,7 +317,8 @@ export class PurchaseButton extends LitElement {
}

render() {
const hasDollar = this.product && this.onPurchaseDollar;
const hasDollar =
(this.product ?? this.dollarPrice) && this.onPurchaseDollar;
const hasHard = this.priceHard !== null && this.onPurchaseHard;
const hasSoft = this.priceSoft !== null && this.onPurchaseSoft;

Expand Down
11 changes: 11 additions & 0 deletions src/client/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@
color: var(--color-gray-400);
}

.custom-plutonium-input {
appearance: textfield;
-moz-appearance: textfield;
}

.custom-plutonium-input::-webkit-outer-spin-button,
.custom-plutonium-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
Expand Down
Loading