mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 16:28:20 +00:00
add in fixed price and also hide consolations
This commit is contained in:
parent
83bf93616f
commit
018d93cfb7
5 changed files with 136 additions and 16 deletions
|
|
@ -31,12 +31,14 @@
|
||||||
item,
|
item,
|
||||||
onClose,
|
onClose,
|
||||||
onTryLuck,
|
onTryLuck,
|
||||||
onConsolation
|
onConsolation,
|
||||||
|
onPurchase
|
||||||
}: {
|
}: {
|
||||||
item: ShopItem;
|
item: ShopItem;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onTryLuck: (orderId: number) => void;
|
onTryLuck: (orderId: number) => void;
|
||||||
onConsolation: (orderId: number, rolled: number, needed: number) => void;
|
onConsolation: (orderId: number, rolled: number, needed: number) => void;
|
||||||
|
onPurchase: (orderId: number) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let activeTab = $state<'leaderboard' | 'wishlist' | 'buyers'>('leaderboard');
|
let activeTab = $state<'leaderboard' | 'wishlist' | 'buyers'>('leaderboard');
|
||||||
|
|
@ -47,7 +49,9 @@
|
||||||
let loadingBuyers = $state(false);
|
let loadingBuyers = $state(false);
|
||||||
let loadingHearts = $state(false);
|
let loadingHearts = $state(false);
|
||||||
let tryingLuck = $state(false);
|
let tryingLuck = $state(false);
|
||||||
|
let purchasing = $state(false);
|
||||||
let showConfirmation = $state(false);
|
let showConfirmation = $state(false);
|
||||||
|
let showBuyConfirmation = $state(false);
|
||||||
let localHearted = $state(item.userHearted);
|
let localHearted = $state(item.userHearted);
|
||||||
let localHeartCount = $state(item.heartCount);
|
let localHeartCount = $state(item.heartCount);
|
||||||
let rollCost = $derived(
|
let rollCost = $derived(
|
||||||
|
|
@ -69,6 +73,7 @@
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
let canAfford = $derived($userScrapsStore >= rollCost);
|
let canAfford = $derived($userScrapsStore >= rollCost);
|
||||||
|
let canAffordFull = $derived($userScrapsStore >= item.price);
|
||||||
let alertMessage = $state<string | null>(null);
|
let alertMessage = $state<string | null>(null);
|
||||||
let alertType = $state<'error' | 'info'>('info');
|
let alertType = $state<'error' | 'info'>('info');
|
||||||
|
|
||||||
|
|
@ -188,10 +193,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleBuyNow() {
|
||||||
|
purchasing = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/shop/items/${item.id}/purchase`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ quantity: 1 })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || data.error) {
|
||||||
|
alertType = 'error';
|
||||||
|
const parts = [data.error || $t.shop.somethingWentWrong];
|
||||||
|
if (data.required !== undefined) parts.push(`need: ${data.required} scraps`);
|
||||||
|
if (data.available !== undefined) parts.push(`have: ${data.available} scraps`);
|
||||||
|
alertMessage = parts.join(' — ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshUserScraps();
|
||||||
|
onPurchase(data.order.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to purchase:', e);
|
||||||
|
alertType = 'error';
|
||||||
|
alertMessage = $t.shop.somethingWentWrong;
|
||||||
|
} finally {
|
||||||
|
purchasing = false;
|
||||||
|
showBuyConfirmation = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleBackdropClick(e: MouseEvent) {
|
function handleBackdropClick(e: MouseEvent) {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
if (showConfirmation) {
|
if (showConfirmation) {
|
||||||
showConfirmation = false;
|
showConfirmation = false;
|
||||||
|
} else if (showBuyConfirmation) {
|
||||||
|
showBuyConfirmation = false;
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +259,12 @@
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||||
onclick={handleBackdropClick}
|
onclick={handleBackdropClick}
|
||||||
onkeydown={(e) =>
|
onkeydown={(e) =>
|
||||||
e.key === 'Escape' && (showConfirmation ? (showConfirmation = false) : onClose())}
|
e.key === 'Escape' &&
|
||||||
|
(showConfirmation
|
||||||
|
? (showConfirmation = false)
|
||||||
|
: showBuyConfirmation
|
||||||
|
? (showBuyConfirmation = false)
|
||||||
|
: onClose())}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
|
|
@ -397,17 +441,27 @@
|
||||||
{$t.shop.soldOut}
|
{$t.shop.soldOut}
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<div class="flex gap-2">
|
||||||
onclick={() => (showConfirmation = true)}
|
<button
|
||||||
disabled={tryingLuck || !canAfford}
|
onclick={() => (showConfirmation = true)}
|
||||||
class="w-full cursor-pointer rounded-full bg-black px-4 py-3 text-lg font-bold text-white transition-all duration-200 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50"
|
disabled={tryingLuck || purchasing || !canAfford}
|
||||||
>
|
class="flex-1 cursor-pointer rounded-full bg-black px-4 py-3 text-lg font-bold text-white transition-all duration-200 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
{#if !canAfford}
|
>
|
||||||
{$t.shop.notEnoughScraps}
|
{#if !canAfford}
|
||||||
{:else}
|
{$t.shop.notEnoughScraps}
|
||||||
{$t.shop.tryYourLuck}
|
{:else}
|
||||||
{/if}
|
{$t.shop.tryYourLuck}
|
||||||
</button>
|
{/if}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={() => (showBuyConfirmation = true)}
|
||||||
|
disabled={tryingLuck || purchasing || !canAffordFull}
|
||||||
|
class="cursor-pointer rounded-full border-4 border-black px-4 py-3 font-bold transition-all duration-200 hover:border-dashed disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
title="{$t.shop.buyNowTooltip}"
|
||||||
|
>
|
||||||
|
<ShoppingBag size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -450,6 +504,39 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if showBuyConfirmation}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 z-60 flex items-center justify-center bg-black/50 p-4"
|
||||||
|
onclick={(e) => e.target === e.currentTarget && (showBuyConfirmation = false)}
|
||||||
|
onkeydown={(e) => e.key === 'Escape' && (showBuyConfirmation = false)}
|
||||||
|
role="dialog"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div class="w-full max-w-md rounded-2xl border-4 border-black bg-white p-6">
|
||||||
|
<h2 class="mb-4 text-2xl font-bold">{$t.shop.confirmBuyNow}</h2>
|
||||||
|
<p class="mb-6 text-gray-600">
|
||||||
|
{$t.shop.confirmBuyNowMessage} <strong>{item.price} {$t.common.scraps}</strong>.
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button
|
||||||
|
onclick={() => (showBuyConfirmation = false)}
|
||||||
|
disabled={purchasing}
|
||||||
|
class="flex-1 cursor-pointer rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 hover:border-dashed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{$t.common.cancel}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={handleBuyNow}
|
||||||
|
disabled={purchasing}
|
||||||
|
class="flex-1 cursor-pointer rounded-full border-4 border-black bg-black px-4 py-2 font-bold text-white transition-all duration-200 hover:border-dashed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{purchasing ? $t.shop.buying : $t.shop.buyNow}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if alertMessage}
|
{#if alertMessage}
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-70 flex items-center justify-center bg-black/50 p-4"
|
class="fixed inset-0 z-70 flex items-center justify-center bg-black/50 p-4"
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,12 @@ export default {
|
||||||
confirmTryLuckMessage: 'are you sure you want to try your luck? this will cost',
|
confirmTryLuckMessage: 'are you sure you want to try your luck? this will cost',
|
||||||
yourChanceLabel: 'your chance:',
|
yourChanceLabel: 'your chance:',
|
||||||
somethingWentWrong: 'something went wrong',
|
somethingWentWrong: 'something went wrong',
|
||||||
failedToTryLuck: 'Failed to try luck'
|
failedToTryLuck: 'Failed to try luck',
|
||||||
|
buyNow: 'buy now',
|
||||||
|
buying: 'buying...',
|
||||||
|
confirmBuyNow: 'confirm purchase',
|
||||||
|
confirmBuyNowMessage: 'buy this item outright for the full price of',
|
||||||
|
buyNowTooltip: 'buy at full price, no gambling'
|
||||||
},
|
},
|
||||||
common: {
|
common: {
|
||||||
cancel: 'cancel',
|
cancel: 'cancel',
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,12 @@ export default {
|
||||||
confirmTryLuckMessage: '¿estás seguro de que quieres probar suerte? esto costará',
|
confirmTryLuckMessage: '¿estás seguro de que quieres probar suerte? esto costará',
|
||||||
yourChanceLabel: 'tu probabilidad:',
|
yourChanceLabel: 'tu probabilidad:',
|
||||||
somethingWentWrong: 'algo salió mal',
|
somethingWentWrong: 'algo salió mal',
|
||||||
failedToTryLuck: 'Error al probar suerte'
|
failedToTryLuck: 'Error al probar suerte',
|
||||||
|
buyNow: 'comprar ahora',
|
||||||
|
buying: 'comprando...',
|
||||||
|
confirmBuyNow: 'confirmar compra',
|
||||||
|
confirmBuyNowMessage: 'comprar este artículo directamente por el precio completo de',
|
||||||
|
buyNowTooltip: 'comprar al precio completo, sin azar'
|
||||||
},
|
},
|
||||||
common: {
|
common: {
|
||||||
cancel: 'cancelar',
|
cancel: 'cancelar',
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@
|
||||||
let filterItem = $state('');
|
let filterItem = $state('');
|
||||||
let filterUser = $state('');
|
let filterUser = $state('');
|
||||||
let filterRegion = $state<'' | 'us' | 'intl'>('');
|
let filterRegion = $state<'' | 'us' | 'intl'>('');
|
||||||
|
let hideConsolations = $state(false);
|
||||||
let theseusLoading = $state<Record<number, boolean>>({});
|
let theseusLoading = $state<Record<number, boolean>>({});
|
||||||
|
|
||||||
let uniqueItems = $derived(
|
let uniqueItems = $derived(
|
||||||
|
|
@ -152,6 +153,10 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideConsolations) {
|
||||||
|
result = result.filter((o) => o.orderType !== 'consolation');
|
||||||
|
}
|
||||||
|
|
||||||
if (dateFrom) {
|
if (dateFrom) {
|
||||||
const from = new Date(dateFrom);
|
const from = new Date(dateFrom);
|
||||||
from.setHours(0, 0, 0, 0);
|
from.setHours(0, 0, 0, 0);
|
||||||
|
|
@ -539,7 +544,15 @@
|
||||||
class="rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 focus:border-dashed focus:outline-none"
|
class="rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 focus:border-dashed focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if dateFrom || dateTo || filterItem || filterUser || filterRegion}
|
<button
|
||||||
|
onclick={() => (hideConsolations = !hideConsolations)}
|
||||||
|
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 {hideConsolations
|
||||||
|
? 'bg-black text-white'
|
||||||
|
: 'hover:border-dashed'}"
|
||||||
|
>
|
||||||
|
hide consolations
|
||||||
|
</button>
|
||||||
|
{#if dateFrom || dateTo || filterItem || filterUser || filterRegion || hideConsolations}
|
||||||
<button
|
<button
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
dateFrom = '';
|
dateFrom = '';
|
||||||
|
|
@ -547,6 +560,7 @@
|
||||||
filterItem = '';
|
filterItem = '';
|
||||||
filterUser = '';
|
filterUser = '';
|
||||||
filterRegion = '';
|
filterRegion = '';
|
||||||
|
hideConsolations = false;
|
||||||
}}
|
}}
|
||||||
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
class="cursor-pointer rounded-xl border-4 border-black px-3 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||||
title="clear filters"
|
title="clear filters"
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,14 @@
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePurchase(orderId: number) {
|
||||||
|
if (selectedItem) {
|
||||||
|
winningItemName = selectedItem.name;
|
||||||
|
}
|
||||||
|
winningOrderId = orderId;
|
||||||
|
selectedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
function handleConsolation(orderId: number, rolled: number, needed: number) {
|
function handleConsolation(orderId: number, rolled: number, needed: number) {
|
||||||
consolationOrderId = orderId;
|
consolationOrderId = orderId;
|
||||||
consolationRolled = rolled;
|
consolationRolled = rolled;
|
||||||
|
|
@ -406,6 +414,7 @@
|
||||||
onClose={() => (selectedItem = null)}
|
onClose={() => (selectedItem = null)}
|
||||||
onTryLuck={handleTryLuck}
|
onTryLuck={handleTryLuck}
|
||||||
onConsolation={handleConsolation}
|
onConsolation={handleConsolation}
|
||||||
|
onPurchase={handlePurchase}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue