Prechádzať zdrojové kódy

add the wallet session and fix bookorder

gdias 2 mesiacov pred
rodič
commit
c447f74c37

+ 1 - 0
src/lib/assets/icons/checkIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2854C5"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/></svg>

+ 0 - 0
src/lib/assets/icons/sidebar/bank.svg → src/lib/assets/icons/sidebar/wallet.svg


+ 25 - 7
src/lib/components/trading/BuyPanel.svelte

@@ -1,29 +1,35 @@
 <script>
   import { createEventDispatcher } from 'svelte';
   export let referencePrice;
+  export let tokens = [];
 
   const dispatch = createEventDispatcher();
 
   let tab = 'market';
   let amountFiat = '';
   let limitPrice = '';
+  let selectedTokenId = tokens?.[0]?.id ?? null;
 
   function formatBRL(n) {
     return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
   }
   function formatEasyToken(n) {
-    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 6, maximumFractionDigits: 6 }).format(Number(n || 0));
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(Math.floor(Number(n || 0)));
   }
 
-  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(referencePrice);
-  $: computedEasyToken = (Number(amountFiat) && priceUsed) ? Number(amountFiat) / priceUsed : 0;
+  $: selectedToken = tokens.find(t => t.id === selectedTokenId);
+  $: refPriceByToken = selectedToken?.price ?? referencePrice;
+  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(refPriceByToken);
+  $: computedEasyTokenRaw = (Number(amountFiat) && priceUsed) ? Number(amountFiat) / priceUsed : 0;
+  $: computedEasyToken = Math.floor(computedEasyTokenRaw);
 
   function confirm() {
     const price = priceUsed;
     const total = Number(amountFiat);
     if (!price || !total) return;
     const amount = computedEasyToken;
-    dispatch('confirm', { type: tab, price, amount, total });
+    if (!amount) return;
+    dispatch('confirm', { type: tab, price, amount, total, tokenId: selectedToken?.id });
     amountFiat = '';
     limitPrice = '';
   }
@@ -38,32 +44,44 @@
     </div>
   </div>
   <div class="p-5 space-y-4">
+    <div>
+      <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tipo de EasyToken</label>
+      <select bind:value={selectedTokenId} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
+        {#each tokens as t}
+          <option value={t.id}>{t.label || t.name}</option>
+        {/each}
+      </select>
+    </div>
+
     {#if tab === 'limit'}
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço limite (R$)</label>
         <input type="number" min="0" step="0.01" bind:value={limitPrice} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
       </div>
     {/if}
+
     <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin (R$)</label>
         <input type="number" min="0" step="0.01" bind:value={amountFiat} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
       </div>
       <div>
-        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyToken</label>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyToken (inteiros)</label>
         <input value={formatEasyToken(computedEasyToken || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
     </div>
+
     <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
       <div>
-        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin a receber</label>
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyToken a receber</label>
         <input value={formatEasyToken(computedEasyToken || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço de referência</label>
-        <input value={referencePrice ? formatBRL(referencePrice) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+        <input value={refPriceByToken ? formatBRL(refPriceByToken) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
     </div>
+
     <div class="flex justify-end">
       <button class="px-4 py-2 rounded bg-green-600 hover:bg-green-700 text-white focus:outline-none focus:ring-2 focus:ring-green-500" on:click={confirm}>Confirmar</button>
     </div>

+ 33 - 15
src/lib/components/trading/SellPanel.svelte

@@ -1,30 +1,36 @@
 <script>
   import { createEventDispatcher } from 'svelte';
   export let referencePrice;
+  export let tokens = [];
 
   const dispatch = createEventDispatcher();
 
   let tab = 'market';
-  let amountEasyToken = '';
+  let amountFiat = '';
   let limitPrice = '';
+  let selectedTokenId = tokens?.[0]?.id ?? null;
 
   function formatBRL(n) {
     return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
   }
   function formatEasyToken(n) {
-    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 6, maximumFractionDigits: 6 }).format(Number(n || 0));
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(Math.floor(Number(n || 0)));
   }
 
-  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(referencePrice);
-  $: computedFiat = (Number(amountEasyToken) && priceUsed) ? Number(amountEasyToken) * priceUsed : 0;
+  $: selectedToken = tokens.find(t => t.id === selectedTokenId);
+  $: refPriceByToken = selectedToken?.price ?? referencePrice;
+  $: priceUsed = tab === 'limit' ? Number(limitPrice) : Number(refPriceByToken);
+  $: numericFiat = Number(amountFiat) || 0;
+  $: computedTokensRaw = (numericFiat && priceUsed) ? numericFiat / priceUsed : 0;
+  $: computedTokens = Math.floor(computedTokensRaw);
 
   function confirm() {
     const price = priceUsed;
-    const amount = Number(amountEasyToken);
-    if (!price || !amount) return;
-    const total = computedFiat;
-    dispatch('confirm', { type: tab, price, amount, total });
-    amountEasyToken = '';
+    const total = numericFiat;
+    const amount = computedTokens;
+    if (!price || !total || !amount) return;
+    dispatch('confirm', { type: tab, price, amount, total, tokenId: selectedToken?.id });
+    amountFiat = '';
     limitPrice = '';
   }
 </script>
@@ -37,32 +43,44 @@
     </div>
   </div>
   <div class="p-5 space-y-4">
+    <div>
+      <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tipo de EasyToken</label>
+      <select bind:value={selectedTokenId} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
+        {#each tokens as t}
+          <option value={t.id}>{t.label || t.name}</option>
+        {/each}
+      </select>
+    </div>
+
     {#if tab === 'limit'}
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço limite (R$)</label>
         <input type="number" min="0" step="0.01" bind:value={limitPrice} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
       </div>
     {/if}
+
     <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
       <div>
-        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyToken</label>
-        <input type="number" min="0" step="0.000001" bind:value={amountEasyToken} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin (R$)</label>
+        <input type="number" min="0" step="0.01" bind:value={amountFiat} class="mt-1 block w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-200 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
       </div>
       <div>
-        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin (R$)</label>
-        <input value={formatBRL(computedFiat || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyToken (inteiros)</label>
+        <input value={formatEasyToken(computedTokens || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
     </div>
+
     <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">EasyCoin a receber</label>
-        <input value={formatBRL(computedFiat || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+        <input value={formatBRL(numericFiat || 0)} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
       <div>
         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Preço de referência</label>
-        <input value={referencePrice ? formatBRL(referencePrice) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
+        <input value={refPriceByToken ? formatBRL(refPriceByToken) : '—'} readonly class="mt-1 block w-full rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 text-gray-900 dark:text-gray-200 px-3 py-2" />
       </div>
     </div>
+
     <div class="flex justify-end">
       <button class="px-4 py-2 rounded bg-red-600 hover:bg-red-700 text-white focus:outline-none focus:ring-2 focus:ring-red-500" on:click={confirm}>Confirmar</button>
     </div>

+ 94 - 0
src/lib/components/wallet/PaymentMenu.svelte

@@ -0,0 +1,94 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  import { fly, fade } from 'svelte/transition';
+  import checkIcon from '$lib/assets/icons/checkIcon.svg?raw';
+
+  export let isOpen = false;
+  export let onClose = () => {};
+  export let onConfirm = (method) => {};
+
+  const dispatch = createEventDispatcher();
+
+  let selected = 'Cartão de Crédito';
+  const methods = [
+    {
+      id: 'card',
+      label: 'Cartão de Crédito',
+      icon: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' class='w-5 h-5'><path d='M2.25 7.5A2.25 2.25 0 014.5 5.25h15a2.25 2.25 0 012.25 2.25v9A2.25 2.25 0 0119.5 18.75h-15A2.25 2.25 0 012.25 16.5v-9zM3.75 9h16.5V7.5a.75.75 0 00-.75-.75h-15a.75.75 0 00-.75.75V9z'/></svg>`
+    },
+    {
+      id: 'pix',
+      label: 'Pix',
+      icon: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' class='w-5 h-5'><path d='M15.75 2.25a3 3 0 012.121.879l3 3a3 3 0 010 4.242l-8.25 8.25a3 3 0 01-4.242 0l-3-3a3 3 0 010-4.242l8.25-8.25A3 3 0 0115.75 2.25z'/></svg>`
+    },
+    {
+      id: 'crypto',
+      label: 'Criptomoeda',
+      icon: `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' class='w-5 h-5'><path d='M12 2.25c5.385 0 9.75 4.365 9.75 9.75S17.385 21.75 12 21.75 2.25 17.385 2.25 12 6.615 2.25 12 2.25zM9 8.25h4.125a2.625 2.625 0 010 5.25H9V8.25zm1.5 3.75h2.625a1.125 1.125 0 100-2.25H10.5v2.25zM9 14.25h4.5a2.25 2.25 0 110 4.5H9v-4.5zm1.5 3h3a.75.75 0 000-1.5h-3v1.5z'/></svg>`
+    }
+  ];
+
+  function close() {
+    onClose();
+    dispatch('close');
+  }
+
+  function confirm() {
+    onConfirm(selected);
+    dispatch('confirm', { method: selected });
+  }
+</script>
+
+{#if isOpen}
+  <div class="fixed inset-0 z-50" aria-modal="true" role="dialog">
+    <div class="absolute inset-0 bg-black/40" on:click={close} transition:fade></div>
+
+    <div
+      class="absolute inset-y-0 right-0 w-full max-w-md bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-700 shadow-xl flex flex-col"
+      transition:fly={{ x: 24, duration: 200 }}
+    >
+      <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
+        <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Selecionar Forma de Pagamento</h3>
+        <button
+          class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
+          aria-label="Fechar"
+          on:click={close}
+        >
+          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
+            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
+          </svg>
+        </button>
+      </div>
+
+      <div class="p-6 space-y-3 overflow-auto">
+        {#each methods as m}
+          <button
+            type="button"
+            class="w-full flex items-center justify-between px-4 py-3 rounded-lg border text-left transition-colors {selected === m.label ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800'}"
+            on:click={() => (selected = m.label)}
+          >
+            <span class="flex items-center gap-3 text-gray-800 dark:text-gray-100">
+              <span class="text-gray-600 dark:text-gray-300">{@html m.icon}</span>
+              {m.label}
+            </span>
+            {#if selected === m.label}
+              <span class="text-blue-600 [&>svg]:w-5 [&>svg]:h-5 [&>svg]:fill-current">{@html checkIcon}</span>
+            {/if}
+          </button>
+        {/each}
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-end gap-3">
+        <button
+          class="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"
+          on:click={close}
+        >
+          Cancelar
+        </button>
+        <button class="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white" on:click={confirm}>
+          Confirmar
+        </button>
+      </div>
+    </div>
+  </div>
+{/if}

+ 105 - 0
src/lib/components/wallet/SellMenu.svelte

@@ -0,0 +1,105 @@
+<script>
+  import { createEventDispatcher } from 'svelte';
+  import { fly, fade } from 'svelte/transition';
+
+  export let isOpen = false;
+  export let onClose = () => {};
+  export let onConfirm = (payload) => {};
+  export let rateBRLPerEasyCoin = 1;
+
+  const dispatch = createEventDispatcher();
+
+  let pixKey = '';
+  let amount = '';
+
+  $: numericAmount = Number(amount) || 0;
+  $: totalBRL = numericAmount * Number(rateBRLPerEasyCoin || 1);
+
+  function close() {
+    onClose();
+    dispatch('close');
+  }
+
+  function confirm() {
+    if (!pixKey || !numericAmount) return;
+    const payload = { pixKey, amount: numericAmount, totalBRL };
+    onConfirm(payload);
+    dispatch('confirm', payload);
+  }
+
+  function formatBRL(n) {
+    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(n || 0));
+  }
+</script>
+
+{#if isOpen}
+  <div class="fixed inset-0 z-50" aria-modal="true" role="dialog">
+    <div class="absolute inset-0 bg-black/40" on:click={close} transition:fade></div>
+
+    <div
+      class="absolute inset-y-0 right-0 w-full max-w-md bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-700 shadow-xl flex flex-col"
+      transition:fly={{ x: 24, duration: 200 }}
+    >
+      <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
+        <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Vender EasyCoin</h3>
+        <button
+          class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
+          aria-label="Fechar"
+          on:click={close}
+        >
+          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
+               fill="none" stroke="currentColor" stroke-width="1.5" class="w-5 h-5">
+            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
+          </svg>
+        </button>
+      </div>
+
+      <div class="p-6 space-y-4 overflow-auto">
+        <div class="space-y-1">
+          <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Chave Pix</label>
+          <input
+            class="w-full px-3 py-2 rounded-md bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
+            bind:value={pixKey}
+            placeholder="Informe sua chave Pix" />
+        </div>
+
+        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+          <div class="space-y-1">
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Quantidade (EasyCoin)</label>
+            <input
+              type="number" min="0" step="0.01"
+              class="w-full px-3 py-2 rounded-md bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
+              bind:value={amount}
+              placeholder="0,00" />
+          </div>
+          <div class="space-y-1">
+            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Valor em Reais</label>
+            <div class="w-full px-3 py-2 rounded-md bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100">
+              {formatBRL(totalBRL)}
+            </div>
+          </div>
+        </div>
+
+        <div class="text-xs text-gray-500 dark:text-gray-400">
+          Taxa de conversão: 1 EasyCoin = {formatBRL(rateBRLPerEasyCoin)}
+        </div>
+      </div>
+
+      <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-end gap-3">
+        <button
+          class="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"
+          on:click={close}
+        >
+          Cancelar
+        </button>
+        <button
+          class="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
+          disabled={!pixKey || !numericAmount}
+          on:click={confirm}
+        >
+          Confirmar
+        </button>
+      </div>
+    </div>
+  </div>
+{/if}

+ 33 - 17
src/lib/layout/SideBar.svelte

@@ -4,7 +4,7 @@
   import cprIcon from '$lib/assets/icons/sidebar/cpr.svg?raw';
   import commoditiesIcon from '$lib/assets/icons/sidebar/commodities.svg?raw';
   import tokensIcon from '$lib/assets/icons/sidebar/tokens.svg?raw';
-  import bankIcon from '$lib/assets/icons/sidebar/bank.svg?raw';
+  import walletIcon from '$lib/assets/icons/sidebar/wallet.svg?raw';
   import operationsIcon from '$lib/assets/icons/sidebar/operations.svg?raw';
   import marketplaceIcon from '$lib/assets/icons/sidebar/marketplace.svg?raw';
   import reportsIcon from '$lib/assets/icons/sidebar/reports.svg?raw';
@@ -12,12 +12,13 @@
   import settingsIcon from '$lib/assets/icons/sidebar/settings.svg?raw';
   import logo from '$lib/assets/logo2.png';
   import { onMount } from 'svelte';
+  import { easyCoinBalance } from '$lib/utils/stores.js';
 
   const navItems = [
     { id: 'dashboard', href: '/dashboard', label: 'Início', icon: dashboardIcon },
     { id: 'cpr', href: '/cpr', label: 'CPR', icon: cprIcon },
     { id: 'commodities', href: '/commodities', label: 'Commodities', icon: commoditiesIcon },
-    { id: 'bank', href: '/bank', label: 'Bank', icon: bankIcon },
+    { id: 'wallet', href: '/wallet', label: 'wallet', icon: walletIcon },
     { id: 'trading', href: '/trading', label: 'Trading', icon: operationsIcon },
     { id: 'marketplace', href: '/marketplace', label: 'Marketplace', icon: marketplaceIcon },
     { id: 'reports', href: '/reports', label: 'Relatórios', icon: reportsIcon },
@@ -49,6 +50,9 @@
     window.addEventListener('resize', onResize);
     return () => window.removeEventListener('resize', onResize);
   });
+  function formatCoin(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(n || 0));
+  }
 </script>
 
 <!-- Botão de toggle (só mobile) -->
@@ -70,26 +74,38 @@
   {/if}
 </button>
 
-<aside class="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 fixed h-full overflow-auto transform transition-transform duration-200 ease-in-out z-40 {isOpen ? 'translate-x-0' : '-translate-x-full'} md:translate-x-0">
+<aside class="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 fixed h-full transform transition-transform duration-200 ease-in-out z-40 {isOpen ? 'translate-x-0' : '-translate-x-full'} md:translate-x-0 flex flex-col">
   <div class="p-5 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center">
     <img src={logo} alt="TooEasy Commodities" class="w-24">
     <h1 class="text-lg font-semibold text-gray-800 dark:text-gray-100 ml-2">TooEasy Commodities</h1>
     <!-- Fechar removido: o botão fixo acima faz o toggle no mobile -->
   </div>
-  <ul class="mt-4">
-    {#each navItems as item}
-      <li>
-        <a
-          href={item.href}
-          on:click={closeSidebar}
-          class="flex items-center w-full px-4 py-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white font-medium border-l-4 {$page.url.pathname.startsWith(item.href) ? 'border-blue-500 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white' : 'border-transparent'}"
-        >
-          <span class="mr-3 [&>svg]:w-5 [&>svg]:h-5 {$page.url.pathname.startsWith(item.href) ? 'text-blue-500' : 'text-gray-500 dark:text-gray-400'}">{@html item.icon}</span>
-          {item.label}
-        </a>
-      </li>
-    {/each}
-  </ul>
+  <div class="flex-1 overflow-auto">
+    <ul class="mt-4">
+      {#each navItems as item}
+        <li>
+          <a
+            href={item.href}
+            on:click={closeSidebar}
+            class="flex items-center w-full px-4 py-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white font-medium border-l-4 {$page.url.pathname.startsWith(item.href) ? 'border-blue-500 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white' : 'border-transparent'}"
+          >
+            <span class="mr-3 [&>svg]:w-5 [&>svg]:h-5 {$page.url.pathname.startsWith(item.href) ? 'text-blue-500' : 'text-gray-500 dark:text-gray-400'}">{@html item.icon}</span>
+            {item.label}
+          </a>
+        </li>
+      {/each}
+    </ul>
+  </div>
+
+  <div class="bg-white dark:bg-gray-900 border rounded-md border-gray-200 dark:border-gray-700 m-2">
+    <div class="flex items-center justify-between text-sm p-3">
+      <div class="flex items-center gap-1.5 text-gray-700 dark:text-gray-200">
+        <span class="[&>svg]:w-4 [&>svg]:h-4 text-gray-500 dark:text-gray-400">{@html walletIcon}</span>
+        <span>EasyCoins</span>
+      </div>
+      <div class="font-medium text-gray-900 dark:text-gray-100">{formatCoin($easyCoinBalance)}</div>
+    </div>
+  </div>
 </aside>
 
 {#if isOpen}

+ 1 - 0
src/lib/utils/stores.js

@@ -2,6 +2,7 @@ import { writable } from 'svelte/store';
 import { browser } from '$app/environment';
 
 export const activeSection = writable('dashboard');
+export const easyCoinBalance = writable(0);
 
 // Função para criar o store de dark mode
 function createDarkModeStore() {

+ 0 - 10
src/routes/bank/+page.svelte

@@ -1,10 +0,0 @@
-<script>
-  import Header from '$lib/layout/Header.svelte';
-  import EmptyState from '$lib/components/ui/EmptyState.svelte';
-  const breadcrumb = [{ label: 'Início' }, { label: 'Bank', active: true }];
-</script>
-
-<div>
-  <Header title="Bank" subtitle="Serviços bancários" breadcrumb={breadcrumb} />
-  <EmptyState title="Área em Desenvolvimento" description="Esta funcionalidade estará disponível em breve." />
-</div>

+ 95 - 0
src/routes/wallet/+page.svelte

@@ -0,0 +1,95 @@
+<script>
+  import Header from '$lib/layout/Header.svelte';
+  import StatsCard from '$lib/components/StatsCard.svelte';
+  import PaymentMenu from '$lib/components/wallet/PaymentMenu.svelte';
+  import SellMenu from '$lib/components/wallet/SellMenu.svelte';
+  import tokensIcon from '$lib/assets/icons/sidebar/tokens.svg?raw';
+  import walletIcon from '$lib/assets/icons/sidebar/wallet.svg?raw';
+
+  const breadcrumb = [{ label: 'Início' }, { label: 'wallet', active: true }];
+
+  let easyTokens = [
+    { id: 1, label: 'EasyToken Soja', amount: 1 },
+    { id: 2, label: 'EasyToken Milho', amount: 1 },
+    { id: 3, name: 'EasyToken Café', balance: 1 }
+  ];
+  let easyCoinBalance = 0;
+
+  let isPaymentOpen = false;
+  let isSellOpen = false;
+
+  let rateBRLPerEasyCoin = 100;
+
+  function openPayment() { isPaymentOpen = true; }
+  function closePayment() { isPaymentOpen = false; }
+  function confirmPayment(method) { isPaymentOpen = false; }
+
+  function openSell() { isSellOpen = true; }
+  function closeSell() { isSellOpen = false; }
+  function confirmSell(e) {
+    isSellOpen = false;
+  }
+
+  function formatToken(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 6 }).format(Number(n || 0));
+  }
+  function formatCoin(n) {
+    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(n || 0));
+  }
+</script>
+
+<div>
+  <Header title="Wallet" subtitle="Saldos e operações" breadcrumb={breadcrumb} />
+
+  <div class="p-4 space-y-6">
+    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
+      {#each easyTokens as t}
+        <div class="rounded-lg overflow-hidden">
+          <StatsCard
+            title={t.label || t.name || 'EasyToken'}
+            value={formatToken(t.amount || t.balance || 0)}
+            change="Saldo atual"
+            iconSvg={tokensIcon}
+          />
+        </div>
+      {/each}
+      <div class="rounded-lg overflow-hidden">
+        <StatsCard
+          title="EasyCoin"
+          value={formatCoin(easyCoinBalance)}
+          change="Saldo atual"
+          iconSvg={walletIcon}
+        />
+      </div>
+    </div>
+
+    <div class="flex gap-3">
+      <button
+        class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white shadow"
+        on:click={openPayment}
+      >
+        Comprar EasyCoins
+      </button>
+
+      <button
+        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"
+        on:click={openSell}
+      >
+        Vender EasyCoins
+      </button>
+    </div>
+  </div>
+
+  <PaymentMenu
+    isOpen={isPaymentOpen}
+    onClose={closePayment}
+    onConfirm={confirmPayment}
+  />
+
+  <SellMenu
+    isOpen={isSellOpen}
+    onClose={closeSell}
+    onConfirm={(e) => confirmSell(e.detail || e)}
+    rateBRLPerEasyCoin={rateBRLPerEasyCoin}
+  />
+</div>