prices: fold groceries and CS skins into one price tracker (/trackers/prices) with tabs
Deploy production / deploy (push) Successful in 1m30s
Deploy production / deploy (push) Successful in 1m30s
- tab registry in #lib/price-trackers (add a tab = route + entry + icon) - groceries -> /trackers/prices/groceries (owner-only), CS prices -> /trackers/prices/cs (public) - old /trackers/groceries/* and /projects/cs-prices/* redirect (301 GET, 308 POST) with query - nav: one prices entry replaces groceries and skins; cyan price-tag icon
This commit is contained in:
1 parent
c175c25615
commit
81b0c51672
29 files changed
+163
-28
No files matched your search
@@ -32,6 +32,7 @@ const staticPaths = [
|
||||
'/trackers/books',
|
||||
'/trackers/ai',
|
||||
'/trackers/radio',
|
||||
'/trackers/prices/cs',
|
||||
'/trackers/spotting',
|
||||
'/trackers/cars',
|
||||
'/trackers/cars/brands',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
// One price tracker, a tab per thing (#lib/price-trackers). Each tab keeps its own page,
|
||||
// loader and data; this only adds the tab bar. Private tabs are only listed for me.
|
||||
import { page } from '$app/state';
|
||||
import Tag from 'lucide-svelte/icons/tag';
|
||||
import SpottingSwitcher from '#lib/components/SpottingSwitcher.svelte';
|
||||
import { visiblePriceTabs } from '#lib/price-trackers.js';
|
||||
|
||||
let { children } = $props();
|
||||
const tabs = $derived(visiblePriceTabs(Boolean(page.data.isOwner)));
|
||||
</script>
|
||||
|
||||
<SpottingSwitcher items={tabs} home="/trackers/prices" label="prices" homeIcon={Tag} />
|
||||
{@render children()}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { isOwner } from '#lib/server/owner.js';
|
||||
import { defaultPriceTab } from '#lib/price-trackers.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
// /trackers/prices has no page of its own: open the first tab this visitor can see.
|
||||
export const load: PageServerLoad = (event) => {
|
||||
redirect(307, defaultPriceTab(isOwner(event)).href);
|
||||
};
|
||||
File renamed without changes.
+5
-5
@@ -28,7 +28,7 @@
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- local, non-reactive query builder
|
||||
const params = new URLSearchParams({ q: browseQuery, kind: browseKind, sort: browseSort });
|
||||
params.set('offset', String(offset));
|
||||
const response = await fetch(`/projects/cs-prices/api/skins?${params}`, { signal });
|
||||
const response = await fetch(`/trackers/prices/cs/api/skins?${params}`, { signal });
|
||||
if (!response.ok) throw new Error(`Search failed (${response.status})`);
|
||||
return (await response.json()).skins as Hit[];
|
||||
}
|
||||
@@ -203,7 +203,7 @@
|
||||
searchError = '';
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/projects/cs-prices/api/search?dimension=${encodeURIComponent(d)}&q=${encodeURIComponent(q)}`,
|
||||
`/trackers/prices/cs/api/search?dimension=${encodeURIComponent(d)}&q=${encodeURIComponent(q)}`,
|
||||
{ signal: controller.signal }
|
||||
);
|
||||
if (!response.ok) throw new Error(`Search failed (${response.status})`);
|
||||
@@ -225,7 +225,7 @@
|
||||
searchError = '';
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/projects/cs-prices/api/search?dimension=${encodeURIComponent(dimension)}&q=${encodeURIComponent(query)}&limit=1000`
|
||||
`/trackers/prices/cs/api/search?dimension=${encodeURIComponent(dimension)}&q=${encodeURIComponent(query)}&limit=1000`
|
||||
);
|
||||
if (!response.ok) throw new Error(`Search failed (${response.status})`);
|
||||
const found = (await response.json()).results as Result[];
|
||||
@@ -262,7 +262,7 @@
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const response = await fetch('/projects/cs-prices/api/series', {
|
||||
const response = await fetch('/trackers/prices/cs/api/series', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: controller.signal,
|
||||
@@ -357,7 +357,7 @@
|
||||
{#each hits as hit (hit.slug)}
|
||||
<a
|
||||
class="hit"
|
||||
href={`/projects/cs-prices/${hit.slug}`}
|
||||
href={`/trackers/prices/cs/${hit.slug}`}
|
||||
style:--rarity={hit.rarity_color ?? 'transparent'}
|
||||
>
|
||||
{#if hit.image}<img
|
||||
File renamed without changes.
+1
-1
@@ -164,7 +164,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="cs-item">
|
||||
<a class="back" href="/projects/cs-prices">← CS prices</a>
|
||||
<a class="back" href="/trackers/prices/cs">← CS prices</a>
|
||||
<header class="head">
|
||||
{#if image}<img src={image} alt="" width="160" height="120" loading="eager" />{/if}
|
||||
<div class="title">
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+3
-3
@@ -254,7 +254,7 @@
|
||||
// ---- merge / move picker ------------------------------------------------------------------
|
||||
async function findGroups(q: string): Promise<Hit[]> {
|
||||
if (!q.trim()) return [];
|
||||
const res = await fetch(`/trackers/groceries/search?q=${encodeURIComponent(q.trim())}`);
|
||||
const res = await fetch(`/trackers/prices/groceries/search?q=${encodeURIComponent(q.trim())}`);
|
||||
return res.ok ? ((await res.json()) as Hit[]) : [];
|
||||
}
|
||||
function openPicker(p: NonNullable<typeof picker>) {
|
||||
@@ -487,7 +487,7 @@
|
||||
<dd>{fmtNum(data.stats.promos)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<a href="/trackers/groceries/stats" class="stats-link">
|
||||
<a href="/trackers/prices/groceries/stats" class="stats-link">
|
||||
<ChartColumn size={16} aria-hidden="true" class="text-violet-500" /> stats
|
||||
</a>
|
||||
</header>
|
||||
@@ -621,7 +621,7 @@
|
||||
· page {data.page} of {fmtNum(pages)}{/if}</span
|
||||
>
|
||||
{#if data.filters.q || data.filters.category || data.filters.store || data.filters.favorites || data.filters.promo || data.filters.multi}
|
||||
<a href="/trackers/groceries" class="clear" data-sveltekit-reset="false"
|
||||
<a href="/trackers/prices/groceries" class="clear" data-sveltekit-reset="false"
|
||||
><X size={12} />Clear</a
|
||||
>
|
||||
{/if}
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -588,7 +588,7 @@
|
||||
<div class="wrap mx-auto max-w-6xl px-4 pt-10 pb-28 sm:px-6 sm:pt-14">
|
||||
<header class="mb-5 flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<a href="/trackers/groceries" class="back"><ArrowLeft size={14} /> groceries</a>
|
||||
<a href="/trackers/prices/groceries" class="back"><ArrowLeft size={14} /> groceries</a>
|
||||
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">groceries stats</h1>
|
||||
<p class="mt-2 flex flex-wrap items-center gap-1.5 text-sm text-[var(--site-muted)]">
|
||||
<Lock size={14} aria-hidden="true" />
|
||||
Reference in new issue
Block a user