diff --git a/website/src/lib/components/self/SEO.svelte b/website/src/lib/components/self/SEO.svelte
index fc1840f..3c07516 100644
--- a/website/src/lib/components/self/SEO.svelte
+++ b/website/src/lib/components/self/SEO.svelte
@@ -5,7 +5,7 @@
title = 'Rugplay',
description = 'Experience realistic cryptocurrency trading simulation game with AI-powered markets, rug pull mechanics, and virtual currencies. Learn crypto trading without financial risk in this educational game.',
type = 'website',
- image = '/rugplay.svg',
+ image = '/apple-touch-icon.png',
imageAlt = 'Rugplay Logo',
keywords = '',
author = 'Outpoot',
diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts
index b5cee9c..cb8030a 100644
--- a/website/src/lib/utils.ts
+++ b/website/src/lib/utils.ts
@@ -31,7 +31,7 @@ export function getTimeBasedGreeting(name: string): string {
export function getPublicUrl(key: string | null): string | null {
if (!key) return null;
- return `${PUBLIC_B2_ENDPOINT}/${PUBLIC_B2_BUCKET}/${key}`;
+ return `/api/proxy/s3/${key}`;
}
export function debounce(func: (...args: any[]) => void, wait: number) {
diff --git a/website/src/routes/+page.svelte b/website/src/routes/+page.svelte
index 0fee172..8ee3466 100644
--- a/website/src/routes/+page.svelte
+++ b/website/src/routes/+page.svelte
@@ -76,7 +76,7 @@
diff --git a/website/src/routes/api/proxy/s3/[...path]/+server.ts b/website/src/routes/api/proxy/s3/[...path]/+server.ts
new file mode 100644
index 0000000..76f1ded
--- /dev/null
+++ b/website/src/routes/api/proxy/s3/[...path]/+server.ts
@@ -0,0 +1,35 @@
+import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT } from "$env/static/public";
+import { error } from '@sveltejs/kit';
+
+export async function GET({ params, request }) {
+ const path = params.path;
+
+ if (!path) {
+ throw error(400, 'Path is required');
+ }
+
+ try {
+ const s3Url = `${PUBLIC_B2_ENDPOINT}/${PUBLIC_B2_BUCKET}/${path}`;
+ const response = await fetch(s3Url);
+
+ if (!response.ok) {
+ throw error(response.status, 'Failed to fetch from S3');
+ }
+
+ const contentType = response.headers.get('content-type') || 'application/octet-stream';
+ const buffer = await response.arrayBuffer();
+
+ return new Response(buffer, {
+ headers: {
+ 'Content-Type': contentType,
+ 'Cache-Control': 'public, max-age=60',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET',
+ 'Access-Control-Allow-Headers': 'Content-Type'
+ }
+ });
+ } catch (e) {
+ console.error('Proxy error:', e);
+ throw error(500, 'Failed to proxy S3 request');
+ }
+}
diff --git a/website/src/routes/coin/[coinSymbol]/+page.server.ts b/website/src/routes/coin/[coinSymbol]/+page.server.ts
new file mode 100644
index 0000000..eead4c5
--- /dev/null
+++ b/website/src/routes/coin/[coinSymbol]/+page.server.ts
@@ -0,0 +1,30 @@
+import { error } from '@sveltejs/kit';
+
+export async function load({ params, url, fetch }) {
+ const { coinSymbol } = params;
+ const timeframe = url.searchParams.get('timeframe') || '1m';
+
+ try {
+ const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${timeframe}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ throw error(404, 'Coin not found');
+ }
+ throw error(500, 'Failed to load coin data');
+ }
+
+ const result = await response.json();
+
+ return {
+ coinSymbol,
+ coin: result.coin,
+ chartData: result.candlestickData || [],
+ volumeData: result.volumeData || [],
+ timeframe
+ };
+ } catch (e) {
+ console.error('Failed to fetch coin data:', e);
+ throw error(500, 'Failed to load coin data');
+ }
+}
diff --git a/website/src/routes/coin/[coinSymbol]/+page.svelte b/website/src/routes/coin/[coinSymbol]/+page.svelte
index a99c394..8e0ddaf 100644
--- a/website/src/routes/coin/[coinSymbol]/+page.svelte
+++ b/website/src/routes/coin/[coinSymbol]/+page.svelte
@@ -31,14 +31,14 @@
const { data } = $props();
let coinSymbol = $derived(data.coinSymbol);
- let coin = $state(null);
- let loading = $state(true);
- let chartData = $state([]);
- let volumeData = $state([]);
+ let coin = $state(data.coin);
+ let loading = $state(false);
+ let chartData = $state(data.chartData);
+ let volumeData = $state(data.volumeData);
let userHolding = $state(0);
let buyModalOpen = $state(false);
let sellModalOpen = $state(false);
- let selectedTimeframe = $state('1m');
+ let selectedTimeframe = $state(data.timeframe || '1m');
let lastPriceUpdateTime = 0;
let shouldSignIn = $state(false);
@@ -54,7 +54,6 @@
];
onMount(async () => {
- await loadCoinData();
await loadUserHolding();
websocketController.setCoin(coinSymbol.toUpperCase());
@@ -92,6 +91,7 @@
async function loadCoinData() {
try {
+ loading = true;
const response = await fetch(`/api/coin/${coinSymbol}?timeframe=${selectedTimeframe}`);
if (!response.ok) {
@@ -274,7 +274,7 @@
1
);
- const processedChartData = chartData.map((candle) => {
+ const processedChartData = chartData.map((candle: { open: any; close: any; high: number; low: number; }) => {
if (candle.open === candle.close) {
const basePrice = candle.open;
const variation = basePrice * 0.001;
@@ -361,14 +361,14 @@
= 0 ? '+' : ''}${coin.change24h.toFixed(2)}%.`
: `Virtual cryptocurrency trading page for ${coinSymbol.toUpperCase()} in the Rugplay simulation game.`}
keywords={coin
? `${coin.name} cryptocurrency game, *${coin.symbol} virtual trading, ${coin.symbol} price simulation, cryptocurrency trading game, virtual coin ${coin.symbol}`
: `${coinSymbol} virtual cryptocurrency, crypto trading simulation, virtual coin trading`}
- image={coin?.icon ? getPublicUrl(coin.icon) : '/rugplay.svg'}
+ image={coin?.icon ? getPublicUrl(coin.icon) : '/apple-touch-icon.png'}
imageAlt={coin ? `${coin.name} (${coin.symbol}) logo` : `${coinSymbol} cryptocurrency logo`}
/>
diff --git a/website/src/routes/coin/[coinSymbol]/+page.ts b/website/src/routes/coin/[coinSymbol]/+page.ts
deleted file mode 100644
index 061fb84..0000000
--- a/website/src/routes/coin/[coinSymbol]/+page.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export async function load({ params }) {
- return {
- coinSymbol: params.coinSymbol
- };
-}
diff --git a/website/src/routes/hopium/[id]/+page.server.ts b/website/src/routes/hopium/[id]/+page.server.ts
new file mode 100644
index 0000000..a92a3d0
--- /dev/null
+++ b/website/src/routes/hopium/[id]/+page.server.ts
@@ -0,0 +1,31 @@
+import { error } from '@sveltejs/kit';
+
+export async function load({ params, fetch }) {
+ const questionId = parseInt(params.id);
+
+ if (isNaN(questionId)) {
+ throw error(400, 'Invalid question ID');
+ }
+
+ try {
+ const response = await fetch(`/api/hopium/questions/${questionId}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ throw error(404, 'Question not found');
+ }
+ throw error(500, 'Failed to load question');
+ }
+
+ const result = await response.json();
+
+ return {
+ questionId,
+ question: result.question || result,
+ probabilityData: result.probabilityHistory || []
+ };
+ } catch (e) {
+ console.error('Failed to fetch question:', e);
+ throw error(500, 'Failed to load question');
+ }
+}
diff --git a/website/src/routes/hopium/[id]/+page.svelte b/website/src/routes/hopium/[id]/+page.svelte
index bc033bd..2e54c3a 100644
--- a/website/src/routes/hopium/[id]/+page.svelte
+++ b/website/src/routes/hopium/[id]/+page.svelte
@@ -28,9 +28,10 @@
import type { PredictionQuestion } from '$lib/types/prediction';
import HopiumQuestionSkeleton from '$lib/components/self/skeletons/HopiumQuestionSkeleton.svelte';
- let question = $state(null);
- let loading = $state(true);
- let probabilityData = $state([]);
+ const { data } = $props();
+ let question = $state(data.question);
+ let loading = $state(false);
+ let probabilityData = $state(data.probabilityData);
// Betting form
let betSide = $state(true);
@@ -38,7 +39,7 @@
let customBetAmount = $state('');
let userBalance = $derived($PORTFOLIO_SUMMARY ? $PORTFOLIO_SUMMARY.baseCurrencyBalance : 0);
- let questionId = $derived(parseInt(page.params.id));
+ let questionId = $derived(data.questionId);
// Chart related
let chartContainer = $state();
@@ -46,7 +47,6 @@
let lineSeries: any = null;
onMount(() => {
- fetchQuestion();
if ($USER_DATA) {
fetchPortfolioSummary();
}
@@ -54,6 +54,7 @@
async function fetchQuestion() {
try {
+ loading = true;
const response = await fetch(`/api/hopium/questions/${questionId}`);
if (response.ok) {
const result = await response.json();
diff --git a/website/src/routes/settings/+page.svelte b/website/src/routes/settings/+page.svelte
index d60e9db..24f13f6 100644
--- a/website/src/routes/settings/+page.svelte
+++ b/website/src/routes/settings/+page.svelte
@@ -288,7 +288,6 @@
diff --git a/website/src/routes/user/[username]/+page.server.ts b/website/src/routes/user/[username]/+page.server.ts
new file mode 100644
index 0000000..b0f5bc3
--- /dev/null
+++ b/website/src/routes/user/[username]/+page.server.ts
@@ -0,0 +1,27 @@
+import { error } from '@sveltejs/kit';
+
+export async function load({ params, fetch }) {
+ const { username } = params;
+
+ try {
+ const response = await fetch(`/api/user/${username}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ throw error(404, 'User not found');
+ }
+ throw error(500, 'Failed to load user profile');
+ }
+
+ const profileData = await response.json();
+
+ return {
+ username,
+ profileData,
+ recentTransactions: profileData.recentTransactions || []
+ };
+ } catch (e) {
+ console.error('Failed to fetch user profile:', e);
+ throw error(500, 'Failed to load user profile');
+ }
+}
diff --git a/website/src/routes/user/[username]/+page.svelte b/website/src/routes/user/[username]/+page.svelte
index 9b6c686..7737d26 100644
--- a/website/src/routes/user/[username]/+page.svelte
+++ b/website/src/routes/user/[username]/+page.svelte
@@ -20,15 +20,14 @@
Activity
} from 'lucide-svelte';
import { goto } from '$app/navigation';
- import type { UserProfileData } from '$lib/types/user-profile';
import { USER_DATA } from '$lib/stores/user-data';
let { data } = $props();
let username = $derived(data.username);
- let profileData = $state(null);
- let recentTransactions = $state([]);
- let loading = $state(true);
+ let profileData = $state(data.profileData);
+ let recentTransactions = $state(data.recentTransactions);
+ let loading = $state(false);
let previousUsername = $state(null);
@@ -37,8 +36,11 @@
);
onMount(async () => {
- await fetchProfileData();
previousUsername = username;
+
+ if (isOwnProfile) {
+ await fetchTransactions();
+ }
});
$effect(() => {
@@ -350,7 +352,7 @@
? `${profileData.profile.bio} - View ${profileData.profile.name}'s simulated trading activity and virtual portfolio in the Rugplay cryptocurrency simulation game.`
: `View @${username}'s profile and simulated trading activity in Rugplay - cryptocurrency trading simulation game platform.`}
type="profile"
- image={profileData?.profile?.image ? getPublicUrl(profileData.profile.image) : '/rugplay.svg'}
+ image={profileData?.profile?.image ? getPublicUrl(profileData.profile.image) : '/apple-touch-icon.png'}
imageAlt={profileData?.profile?.name
? `${profileData.profile.name}'s profile picture`
: `@${username}'s profile`}
diff --git a/website/src/routes/user/[username]/+page.ts b/website/src/routes/user/[username]/+page.ts
deleted file mode 100644
index 253a906..0000000
--- a/website/src/routes/user/[username]/+page.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export async function load({ params }) {
- return {
- username: params.username
- };
-}