Files
ggdeals/app/Views/wishlist.php
T
deepseek fff7a4b9b9 GG.deals clone: CodeIgniter 4 + MongoDB game price tracker
- Full gg.deals-style UI: deal feed with filters, game pages with price
  history charts, rankings, vouchers, news, prepaids, subscriptions,
  bundles, franchises, wishlist, API docs
- Real data crawlers:
  * gg:crawl (Steam Store API + GOG Catalog + Epic free games)
  * gg:catalog (252K game slugs from gg.deals sitemaps)
  * gg:ggdeals (per-game prices via FlareSolverr session, JSON-LD parsing)
  * gg:seed / gg:crawl-covers (demo seed + Steam CDN cover art)
- 24/7 self-updating scheduler (scripts/scheduler_loop.php, lockfile-protected)
- Tailwind v4 dark/light theme, Alpine.js, Chart.js
2026-08-23 07:06:02 +00:00

72 lines
3.7 KiB
PHP

<?= $this->extend('layout') ?>
<?= $this->section('content') ?>
<div class="mb-6">
<h1 class="text-2xl font-extrabold text-white tracking-tight">Your wishlist</h1>
<p class="text-sm text-muted mt-1">
<span x-text="$store.gg.wishlist.length"></span> game<span x-show="$store.gg.wishlist.length !== 1">s</span> saved · tracked in your browser
</p>
</div>
<div x-data="wishlistPage()" x-init="load()" class="mb-6">
<div x-show="!loaded" class="text-center text-muted py-10 text-sm">Loading…</div>
<div x-show="loaded && games.length === 0" x-cloak class="bg-card border border-line rounded-xl p-10 text-center">
<div class="text-4xl mb-3">💜</div>
<div class="text-zinc-200 font-semibold">Your wishlist is empty</div>
<p class="text-muted text-sm mt-1">Browse the deals and click the heart on any game to save it here.</p>
<a href="/" class="inline-block mt-4 bg-accent text-accent-ink font-bold text-sm py-2 px-5 rounded-lg hover:bg-accent/90">Browse deals</a>
</div>
<div x-show="games.length" x-cloak class="space-y-2.5">
<template x-for="g in games" :key="g.slug">
<div class="bg-card border border-line rounded-xl p-3 flex items-center gap-4 hover:border-accent/40 transition-colors">
<a :href="'/game/' + g.slug" class="shrink-0 rounded-lg overflow-hidden ring-1 ring-line">
<img :src="g.cover.file" class="w-[132px] h-[62px] object-cover" alt="">
</a>
<div class="min-w-0 flex-1">
<a :href="'/game/' + g.slug" class="text-[15px] font-semibold text-zinc-100 hover:text-accent truncate block" x-text="g.title"></a>
<div class="mt-1 text-xs text-muted flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="score-chip" x-text="'MC ' + g.score"></span>
<span x-text="(g.genres || []).slice(0, 2).join(' · ')"></span>
<span>
<span class="text-zinc-400" x-text="g.active_deals"></span> offers
</span>
</div>
</div>
<div class="text-right shrink-0">
<div class="flex items-center gap-2 justify-end">
<span class="text-2xl font-extrabold text-accent leading-none" x-text="'$' + Number(g.best_keyshop.price).toFixed(2)"></span>
</div>
<div class="text-xs text-muted line-through mt-1" x-text="'$' + Number(g.base_price).toFixed(2)"></div>
</div>
<button @click="$store.gg.toggleWishlist(g.slug)"
class="shrink-0 w-9 h-9 rounded-lg bg-elev border border-line flex items-center justify-center text-accent hover:border-accent/50 transition-colors"
title="Remove from wishlist">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
</button>
</div>
</template>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
function wishlistPage() {
return {
games: [],
loaded: false,
async load() {
const slugs = Alpine.store('gg').wishlist;
if (slugs.length === 0) { this.loaded = true; return; }
const res = await fetch('/api/wishlist?slugs[]=' + slugs.map(encodeURIComponent).join('&slugs[]='));
this.games = await res.json();
this.loaded = true;
}
}
}
</script>
<?= $this->endSection() ?>