Skip to content

Commit a7d3681

Browse files
committed
Clean up market page
1 parent 79cd341 commit a7d3681

File tree

5 files changed

+87
-108
lines changed

5 files changed

+87
-108
lines changed

src/lib/components/MarketItem.svelte

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/lib/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,26 @@ export default function fileSizeFromUrl(url: string): Promise<number> {
5555
export function formatMinutes(mins: number | null) {
5656
return Math.floor((mins ?? 0) / 60) + 'h ' + Math.floor((mins ?? 0) % 60) + 'min';
5757
}
58+
59+
export function calculateMarketPrice(
60+
minPrice: number,
61+
maxPrice: number,
62+
minShopScore: number,
63+
maxShopScore: number,
64+
userShopScore: number
65+
) {
66+
if (userShopScore <= minShopScore) {
67+
return maxPrice;
68+
} else if (userShopScore >= maxShopScore) {
69+
return minPrice;
70+
} else {
71+
const priceDiff = maxPrice - minPrice;
72+
const shopScoreDiff = maxShopScore - minShopScore;
73+
const m = priceDiff / shopScoreDiff; // diff_y/diff_x
74+
75+
const shopScoreRemainder = userShopScore - minShopScore;
76+
77+
// y = -mx + c
78+
return Math.round(-m * shopScoreRemainder + maxPrice);
79+
}
80+
}

src/routes/dashboard/market/+page.server.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { db } from '$lib/server/db/index.js';
22
import { marketItem } from '$lib/server/db/schema.js';
3+
import { calculateMarketPrice } from '$lib/utils';
34
import { error } from '@sveltejs/kit';
45
import { eq, and } from 'drizzle-orm';
56

@@ -21,40 +22,23 @@ export async function load({ locals }) {
2122
minRequiredShopScore: marketItem.minRequiredShopScore
2223
})
2324
.from(marketItem)
24-
.where(
25-
and(
26-
eq(marketItem.deleted, false),
27-
locals.user.hasAdmin ? undefined : eq(marketItem.isPublic, true)
28-
)
29-
)
25+
.where(and(eq(marketItem.deleted, false), eq(marketItem.isPublic, true)))
3026
.orderBy(marketItem.maxPrice);
3127

32-
const shopScore = Number(locals.user?.shopScore || 0);
28+
const shopScore = locals.user.shopScore;
29+
3330
const marketItemsWithPrice = marketItems
3431
.map((item) => {
35-
const max = Number(item.maxPrice || 0);
36-
const min = Number(item.minPrice || 0);
37-
const diff = Math.max(0, max - min);
38-
39-
const minShop = Number(item.minShopScore || 0);
40-
const maxShop = Number(item.maxShopScore || 0);
41-
let discountPercent = 0;
42-
if (maxShop > minShop) {
43-
discountPercent = (shopScore - minShop) / (maxShop - minShop);
44-
discountPercent = Math.max(0, Math.min(1, discountPercent));
45-
} else {
46-
discountPercent = 0;
47-
}
32+
const computedPrice = calculateMarketPrice(
33+
item.minPrice,
34+
item.maxPrice,
35+
item.minShopScore,
36+
item.maxShopScore,
37+
shopScore
38+
);
4839

49-
const discountAmount = diff * discountPercent;
50-
const rawPrice = Math.ceil(max - discountAmount);
51-
const computedPrice = Math.max(rawPrice, min);
52-
return { ...item, computedPrice };
53-
})
54-
.filter((item) => {
55-
if (locals.user?.hasAdmin) return true;
56-
const minReq = Number(item.minRequiredShopScore || 0);
57-
return shopScore >= minReq;
40+
const discountAmount = 1 - computedPrice / item.maxPrice;
41+
return { ...item, computedPrice, discountAmount };
5842
})
5943
.sort((a, b) => a.computedPrice - b.computedPrice);
6044

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,26 @@
11
<script lang="ts">
22
import Head from '$lib/components/Head.svelte';
3+
import MarketItem from './MarketItem.svelte';
34
import MarketTimer from './MarketTimer.svelte';
45
56
let { data } = $props();
6-
7-
function computeUserPrice(item: any) {
8-
if (typeof item.computedPrice === 'number') return item.computedPrice;
9-
10-
const max = Number(item.maxPrice || 0);
11-
const min = Number(item.minPrice || 0);
12-
const diff = Math.max(0, max - min);
13-
const shopScore = Number(data?.user?.shopScore || 0);
14-
15-
const minShop = Number(item.minShopScore || 0);
16-
const maxShop = Number(item.maxShopScore || 0);
17-
let discountPercent = 0;
18-
if (maxShop > minShop) {
19-
discountPercent = (shopScore - minShop) / (maxShop - minShop);
20-
discountPercent = Math.max(0, Math.min(1, discountPercent));
21-
} else {
22-
discountPercent = 0;
23-
}
24-
25-
const discountAmount = diff * discountPercent;
26-
const rawPrice = Math.ceil(max - discountAmount);
27-
const price = Math.max(rawPrice, min);
28-
return price;
29-
}
30-
31-
function getPriceInfo(item: any) {
32-
const max = Number(item.maxPrice || 0);
33-
const price = computeUserPrice(item);
34-
const save = Math.max(0, max - price);
35-
const hasDiscount = save > 0 && max > 0;
36-
const percentOff = hasDiscount ? Math.ceil((save / max) * 100) : 0;
37-
return { price, save, hasDiscount, percentOff };
38-
}
39-
40-
function formatClays(value: number | string) {
41-
const n = Number(value || 0);
42-
return `${n.toLocaleString()} clays`;
43-
}
447
</script>
458

469
<Head title="Market" />
4710

48-
<h1 class="mt-5 mb-3 font-hero text-3xl font-medium">Market</h1>
11+
<h1 class="mt-5 mb-2 font-hero text-3xl font-medium">Market</h1>
4912

5013
{#if data.marketItems.length === 0}
5114
<MarketTimer />
5215
{:else}
53-
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 mb-5">
16+
<p class="mb-2">
17+
Market score: <span class="rounded-xl bg-primary-800 px-1">{data.user.shopScore}</span>
18+
<span class="opacity-50">(allows you to get stuff for cheaper and unlock more items!)</span>
19+
</p>
20+
21+
<div class="mb-5 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
5422
{#each data.marketItems as item (item.id)}
55-
<div class="themed-box p-4 flex flex-col gap-3">
56-
<div class="aspect-square rounded-lg overflow-hidden mb-3 bg-primary-800/10">
57-
<img src={item.image} alt={item.name} class="w-full h-full object-contain object-center" />
58-
</div>
59-
<div>
60-
<h3 class="text-lg font-bold mb-1">{item.name}</h3>
61-
<p class="text-sm text-primary-300 leading-snug">{item.description}</p>
62-
{#if getPriceInfo(item).hasDiscount}
63-
<div class="flex items-center gap-3 mt-3">
64-
<div class="text-sm text-primary-300 line-through">{formatClays(item.maxPrice)}</div>
65-
<div class="ml-auto text-lg font-bold text-emerald-500">{formatClays(getPriceInfo(item).price)}</div>
66-
</div>
67-
<div class="flex items-center justify-between mt-1 text-sm text-primary-300">
68-
<div class="inline-flex items-center gap-2 px-2 py-0.5 bg-red-100 text-red-600 rounded font-semibold">{getPriceInfo(item).percentOff}% off</div>
69-
<div class="text-sm">You save {formatClays(getPriceInfo(item).save)}</div>
70-
</div>
71-
{:else}
72-
<div class="mt-3 text-lg font-bold">{formatClays(getPriceInfo(item).price)}</div>
73-
{/if}
74-
</div>
75-
</div>
23+
<MarketItem {item} />
7624
{/each}
7725
</div>
7826
{/if}
79-
80-
<!-- <p>Market score: {data.user.shopScore}</p> -->
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts">
2+
let { item, url = null } = $props();
3+
</script>
4+
5+
<!-- <div
6+
class={`themed-box relative flex flex-col p-3 shadow-lg/20 transition-all ${url ? 'hover:scale-102' : ''}`}
7+
>
8+
{#if url}
9+
<a class="absolute inset-0 z-1" href={url} aria-label="Market item"></a>
10+
{/if}
11+
<div>
12+
13+
</div>
14+
</div> -->
15+
16+
<div class="themed-box flex flex-col gap-3 p-3">
17+
<div class="aspect-square overflow-hidden rounded-lg bg-primary-800/10">
18+
<img src={item.image} alt={item.name} class="h-full w-full object-contain object-center" />
19+
</div>
20+
<div>
21+
<h3 class="text-xl font-bold">{item.name}</h3>
22+
<p class="leading-snug text-primary-300 mb-1">{item.description}</p>
23+
{#if item.discountAmount > 0}
24+
<div class="flex items-center gap-2">
25+
<div class="text-lg text-primary-300 line-through">{item.maxPrice} bricks</div>
26+
<div class="text-lg font-bold text-emerald-500">
27+
{item.computedPrice} bricks
28+
</div>
29+
</div>
30+
<div class="mt-1 flex items-center justify-between text-sm text-primary-300">
31+
<div
32+
class="inline-flex items-center gap-2 rounded bg-red-100 px-2 py-0.5 font-semibold text-red-600"
33+
>
34+
{Math.round(item.discountAmount * 100)}% off
35+
</div>
36+
<div class="text-sm">You save {item.maxPrice - item.computedPrice} bricks</div>
37+
</div>
38+
{:else}
39+
<div class="text-lg font-bold">{item.computedPrice} bricks</div>
40+
{/if}
41+
</div>
42+
</div>

0 commit comments

Comments
 (0)