feat(#54): Add credit usage dashboard

This commit is contained in:
Josh Creek
2024-11-13 12:00:59 +00:00
parent 62f7525238
commit 93f41de185
5 changed files with 159 additions and 0 deletions
+22
View File
@@ -451,6 +451,28 @@ const getUserCredits = async (userId: string) => {
return creditData.credits_remaining;
};
/**
* Fetches the credit transaction history for a specific user.
*
* @param userId - The ID of the user whose transactions are to be fetched.
* @returns Array of transaction records containing credits_change, description, and created_at.
*/
export const getUserCreditTransactions = async (userId: string) => {
if (!userId) throw new Error('User ID is required');
const { data, error } = await supabaseAdmin
.from('credit_transactions')
.select('credits_change, description, created_at')
.eq('user_id', userId)
.order('created_at', { ascending: false }); // Orders transactions with the latest ones first
if (error) {
throw new Error(`Error fetching transactions: ${error.message}`);
}
return data || [];
};
type ProductWithPrices = Product & {
prices: Price[];
actualPrice?: number;
+29
View File
@@ -0,0 +1,29 @@
import { redirect } from '@sveltejs/kit';
import { getUserCredits, getUserCreditTransactions } from '$lib/utils/supabase/admin';
export async function load({ locals: { safeGetSession } }) {
const { session } = await safeGetSession();
if (!session) {
redirect(303, '/');
}
const userId = session.user?.id;
if (!userId) {
return { error: 'User not authenticated' };
}
try {
const creditsRemaining = await getUserCredits(userId);
const transactions = await getUserCreditTransactions(userId);
return {
creditsRemaining,
transactions
};
} catch (error) {
return {
error: error.message
};
}
}
+91
View File
@@ -0,0 +1,91 @@
<script lang="ts">
import { onMount } from 'svelte';
import Chart, { type ChartData } from 'chart.js/auto';
export let data;
const { creditsRemaining, transactions } = data;
let chartCanvas: HTMLCanvasElement | null = null;
let creditsTrendChart: Chart | null = null;
let noTransactions = transactions ? transactions.length === 0 : true;
let chartData: ChartData | null = null;
if (!noTransactions) {
chartData = {
labels: transactions.map((t) => new Date(t.created_at).toLocaleDateString()),
datasets: [
{
label: 'Credit Changes',
data: transactions.map((t) => t.credits_change),
backgroundColor: transactions.map((t) => (t.credits_change > 0 ? 'green' : 'red'))
}
]
};
}
onMount(() => {
if (chartCanvas && chartData) {
creditsTrendChart = new Chart(chartCanvas, {
type: 'bar',
data: chartData,
options: { responsive: true }
});
}
});
$: if (transactions && chartData) {
chartData.labels = transactions.map((t) => new Date(t.created_at).toLocaleDateString());
chartData.datasets[0].data = transactions.map((t) => t.credits_change);
creditsTrendChart?.update();
}
</script>
<main class="flex flex-col items-center p-4">
<h1 class="text-3xl font-bold mb-4">Credit Dashboard</h1>
<p class="text-lg">Credits Remaining: {creditsRemaining}</p>
<div class="w-full md:w-3/4 mt-6">
<canvas bind:this={chartCanvas} class="my-8"></canvas>
</div>
<h2 class="text-2xl font-semibold mb-2">Transaction History</h2>
<div class="overflow-x-auto w-full md:w-3/4">
{#if noTransactions}
<p class="text-lg">No transactions found.</p>
{:else}
<table class="table-auto w-full text-left border-collapse">
<thead>
<tr class="border-b">
<th class="px-4 py-2">Date</th>
<th class="px-4 py-2">Description</th>
<th class="px-4 py-2">Change</th>
</tr>
</thead>
<tbody>
{#each transactions as transaction}
<tr class="border-b hover:bg-gray-100">
<td class="px-4 py-2">{new Date(transaction.created_at).toLocaleDateString()}</td>
<td class="px-4 py-2">{transaction.description}</td>
<td
class="px-4 py-2"
class:text-green-600={transaction.credits_change > 0}
class:text-red-600={transaction.credits_change < 0}
>
{transaction.credits_change > 0 ? '+' : ''}{transaction.credits_change}
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
</main>
<style>
main {
text-align: center;
}
table {
margin-top: 1rem;
}
</style>