+page.svelte 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <script>
  2. import { onMount } from 'svelte';
  3. import { get } from 'svelte/store';
  4. import Header from '$lib/layout/Header.svelte';
  5. import StatsCard from '$lib/components/StatsCard.svelte';
  6. import PaymentMenu from '$lib/components/wallet/PaymentMenu.svelte';
  7. import SellMenu from '$lib/components/wallet/SellMenu.svelte';
  8. import tokensIcon from '$lib/assets/icons/sidebar/tokens.svg?raw';
  9. import walletIcon from '$lib/assets/icons/sidebar/wallet.svg?raw';
  10. import { authToken } from '$lib/utils/stores';
  11. const breadcrumb = [{ label: 'Início' }, { label: 'wallet', active: true }];
  12. const apiUrl = import.meta.env.VITE_API_URL;
  13. let walletTokens = [];
  14. let easyCoinBalance = 0;
  15. let tokensLoading = false;
  16. let tokensError = '';
  17. let isPaymentOpen = false;
  18. let isSellOpen = false;
  19. let rateBRLPerEasyCoin = 100;
  20. onMount(() => {
  21. void fetchWalletTokens();
  22. });
  23. function openPayment() {
  24. isPaymentOpen = true;
  25. }
  26. function closePayment() {
  27. isPaymentOpen = false;
  28. }
  29. function confirmPayment(method) {
  30. isPaymentOpen = false;
  31. }
  32. function openSell() {
  33. isSellOpen = true;
  34. }
  35. function closeSell() {
  36. isSellOpen = false;
  37. }
  38. function confirmSell(e) {
  39. isSellOpen = false;
  40. }
  41. async function parseResponse(res) {
  42. const raw = await res.text();
  43. return raw ? JSON.parse(raw) : null;
  44. }
  45. async function fetchWalletTokens() {
  46. if (!apiUrl) return;
  47. tokensLoading = true;
  48. tokensError = '';
  49. try {
  50. const token = get(authToken);
  51. if (!token) {
  52. throw new Error('Sessão expirada. Faça login novamente.');
  53. }
  54. const res = await fetch(`${apiUrl}/wallet/tokens`, {
  55. method: 'POST',
  56. headers: {
  57. 'content-type': 'application/json',
  58. Authorization: `Bearer ${token}`
  59. }
  60. });
  61. const body = await parseResponse(res);
  62. if (!res.ok || body?.status !== 'ok') {
  63. throw new Error(body?.msg ?? 'Falha ao carregar tokens da wallet.');
  64. }
  65. const payload = body?.data ?? {};
  66. walletTokens = Array.isArray(payload?.tokens) ? payload.tokens : [];
  67. easyCoinBalance = resolveEasyCoinBalance(payload);
  68. } catch (err) {
  69. console.error('[Wallet] Erro ao buscar tokens:', err);
  70. walletTokens = [];
  71. easyCoinBalance = 0;
  72. tokensError = err?.message ?? 'Não foi possível carregar os tokens.';
  73. } finally {
  74. tokensLoading = false;
  75. }
  76. }
  77. function resolveEasyCoinBalance(payload) {
  78. const candidates = [
  79. payload?.wallet?.easycoin_balance,
  80. payload?.wallet?.easyCoinBalance,
  81. payload?.wallet?.balance,
  82. payload?.easycoin_balance,
  83. payload?.easyCoinBalance,
  84. payload?.easycoin,
  85. payload?.balance
  86. ];
  87. const firstValue = candidates.find((value) => value !== undefined && value !== null && value !== '');
  88. return Number(firstValue ?? 0);
  89. }
  90. function formatToken(n) {
  91. return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 6 }).format(Number(n || 0));
  92. }
  93. function formatCoin(n) {
  94. return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(n || 0));
  95. }
  96. function tokenCardLabel(token) {
  97. return token?.cpr_product_name ?? token?.token_content ?? token?.token_external_id ?? token?.token_id ?? 'EasyToken';
  98. }
  99. function tokenCardSubtitle(token) {
  100. if (token?.token_city && token?.token_uf) {
  101. return `${token.token_city}/${token.token_uf}`;
  102. }
  103. if (token?.token_external_id) {
  104. return `ID ${token.token_external_id}`;
  105. }
  106. return 'Saldo atual';
  107. }
  108. function tokenAmount(token) {
  109. const candidates = [
  110. token?.token_commodities_amount,
  111. token?.token_amount,
  112. token?.token_balance,
  113. token?.balance,
  114. token?.amount
  115. ];
  116. const value = candidates.find((item) => item !== undefined && item !== null && item !== '');
  117. return Number(value ?? 0);
  118. }
  119. </script>
  120. <div>
  121. <Header title="Wallet" subtitle="Saldos e operações" breadcrumb={breadcrumb} />
  122. <div class="p-4 space-y-6">
  123. {#if tokensError}
  124. <div class="rounded border border-red-200 bg-red-50 text-red-700 px-3 py-2 text-sm">{tokensError}</div>
  125. {/if}
  126. <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  127. {#if tokensLoading && !walletTokens.length}
  128. <div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-800/60 p-4 text-center text-sm text-gray-500">
  129. Carregando tokens...
  130. </div>
  131. {:else if walletTokens.length}
  132. {#each walletTokens as token (token?.token_external_id ?? token?.token_id ?? token?.cpr_id ?? token)}
  133. <div class="rounded-lg overflow-hidden">
  134. <StatsCard
  135. title={tokenCardLabel(token)}
  136. value={formatToken(tokenAmount(token))}
  137. change={tokenCardSubtitle(token)}
  138. iconSvg={tokensIcon}
  139. />
  140. </div>
  141. {/each}
  142. {:else}
  143. <div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-800/60 p-4 text-center text-sm text-gray-500">
  144. Nenhum token encontrado para sua conta.
  145. </div>
  146. {/if}
  147. <div class="rounded-lg overflow-hidden">
  148. <StatsCard
  149. title="EasyCoin"
  150. value={formatCoin(easyCoinBalance)}
  151. change={tokensLoading ? 'Atualizando...' : 'Saldo atual'}
  152. iconSvg={walletIcon}
  153. />
  154. </div>
  155. </div>
  156. <div class="flex gap-3">
  157. <button
  158. class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white shadow"
  159. on:click={openPayment}
  160. >
  161. Comprar EasyCoins
  162. </button>
  163. <button
  164. class="inline-flex items-center gap-2 px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-800 dark:text-gray-100"
  165. on:click={openSell}
  166. >
  167. Vender EasyCoins
  168. </button>
  169. </div>
  170. </div>
  171. <PaymentMenu
  172. isOpen={isPaymentOpen}
  173. onClose={closePayment}
  174. onConfirm={confirmPayment}
  175. />
  176. <SellMenu
  177. isOpen={isSellOpen}
  178. onClose={closeSell}
  179. onConfirm={(e) => confirmSell(e.detail || e)}
  180. rateBRLPerEasyCoin={rateBRLPerEasyCoin}
  181. />
  182. </div>