diff --git a/frontend/src/stores/crypto.test.ts b/frontend/src/stores/crypto.test.ts index 07b1987..cc5d02b 100644 --- a/frontend/src/stores/crypto.test.ts +++ b/frontend/src/stores/crypto.test.ts @@ -15,7 +15,17 @@ const bootstrap: CryptoBootstrap = { holdings: [], markets: [], portfolioValue: '25000', - profile: { handle: 'skyline', id: 'profile-1', status: 'active' }, + profile: { + createdAt: Date.now() - 86_400_000, + handle: 'skyline', + hideBalances: false, + id: 'profile-1', + priceAlerts: true, + status: 'active', + totalTrades: 12, + totalVolume: '18462.80', + tradeConfirmations: true, + }, } const quote: CryptoQuote = { expiresAt: Date.now() + 8000, @@ -84,4 +94,26 @@ describe('crypto store', () => { expect(crypto.data).toEqual(bootstrap) expect(crypto.error).toBe('quote_expired') }) + + it('updates profile preferences through the authenticated server endpoint', async () => { + mockNuiCall.mockResolvedValueOnce({ data: bootstrap, success: true }) + const crypto = useCryptoStore() + + expect( + await crypto.updateProfile({ + handle: 'skyline', + hideBalances: true, + password: '', + priceAlerts: false, + tradeConfirmations: true, + }), + ).toBe(true) + expect(mockNuiCall).toHaveBeenCalledWith('crypto:update-profile', { + handle: 'skyline', + hideBalances: true, + password: '', + priceAlerts: false, + tradeConfirmations: true, + }) + }) }) diff --git a/frontend/src/stores/crypto.ts b/frontend/src/stores/crypto.ts index 19e0f22..3162195 100644 --- a/frontend/src/stores/crypto.ts +++ b/frontend/src/stores/crypto.ts @@ -95,5 +95,20 @@ export const useCryptoStore = defineStore('crypto', { this.pendingQuote = null return true }, + async updateProfile(payload: { + handle: string + hideBalances: boolean + password: string + priceAlerts: boolean + tradeConfirmations: boolean + }): Promise { + const response = await this.call( + 'update-profile', + payload, + ) + if (!response.success || !response.data) return false + this.data = response.data + return true + }, }, }) diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index ce6df27..e1d67f1 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -440,7 +440,12 @@ const cryptoFallbackLocales = { loginAction: 'Open portfolio', registerAction: 'Create secure profile', }, - tabs: { portfolio: 'Portfolio', markets: 'Markets', activity: 'Activity' }, + tabs: { + portfolio: 'Portfolio', + markets: 'Markets', + activity: 'Activity', + profile: 'Profile', + }, portfolio: { total: 'Total portfolio', cash: 'Available cash', @@ -448,7 +453,11 @@ const cryptoFallbackLocales = { empty: 'No assets yet', emptyBody: 'Open Markets to request a protected quote.', avg: 'Avg.', + insightTitle: 'Portfolio insight', + insightBody: + 'Your holdings are valued against the latest synthetic server prices.', }, + quick: { trade: 'Trade', more: 'More' }, actions: { deposit: 'Deposit', withdraw: 'Withdraw' }, markets: { title: 'Fictional markets', @@ -456,6 +465,22 @@ const cryptoFallbackLocales = { noticeTitle: 'In-game exchange', notice: 'Prices are synthetic and have no real-world value. Every trade is checked against finite exchange liquidity.', + eyebrow: 'Market pulse', + movers: 'Top movers', + today: 'Today', + }, + marketDetail: { + back: 'Back to markets', + today: 'Today', + description: + 'The reference price is generated by the server. Executable prices use a protected short-lived quote.', + investment: 'Your investment', + totalValue: 'Current value', + statistics: 'Market statistics', + high24h: '24h high', + low24h: '24h low', + supply: 'Issued supply', + liquidity: 'Exchange liquidity', }, trade: { buy: 'Buy', @@ -482,6 +507,31 @@ const cryptoFallbackLocales = { title: 'Financial activity', empty: 'No activity', emptyBody: 'Deposits, withdrawals and trades will appear here.', + volume: 'Lifetime trading volume', + completedTrades: 'completed trades', + cash: 'Cash', + filters: { all: 'All', trades: 'Trades', wallet: 'Wallet' }, + }, + profile: { + verified: 'Character ownership verified', + trades: 'Trades', + volume: 'Volume', + memberSince: 'Member since', + preferences: 'Preferences', + priceAlerts: 'Price alerts', + priceAlertsBody: 'Notify me about notable market moves.', + confirmations: 'Trade confirmations', + confirmationsBody: 'Keep an extra confirmation before execution.', + hideBalances: 'Privacy mode', + hideBalancesBody: 'Hide balances across VaultX.', + identity: 'Profile identity', + passwordForChange: 'Password for handle change', + passwordOptional: 'Only required when changing handle', + saved: 'Profile saved securely.', + save: 'Save profile', + securityTitle: 'Protected profile', + securityBody: + 'Your profile stays bound to this framework character and financial session.', }, activityTypes: { buy: 'Asset purchase', @@ -516,6 +566,7 @@ const cryptoFallbackLocales = { settlement_pending: 'A money transfer is already pending review.', service_unavailable: 'VaultX is temporarily unavailable.', request_failed: 'The secure exchange request failed.', + invalid_profile: 'Check your profile settings and handle.', default: 'VaultX could not complete the request.', }, } diff --git a/frontend/src/types/crypto.ts b/frontend/src/types/crypto.ts index ddb5cf9..2e2bcae 100644 --- a/frontend/src/types/crypto.ts +++ b/frontend/src/types/crypto.ts @@ -4,11 +4,15 @@ export type CryptoMarket = { changePercent: number color: string enabled: boolean + high24h: string id: string + issuedSupply: string + low24h: string name: string price: string sparkline: number[] symbol: string + treasuryAvailable: string } export type CryptoHolding = { @@ -28,9 +32,15 @@ export type CryptoActivity = { } export type CryptoProfile = { + createdAt: number handle: string + hideBalances: boolean id: string + priceAlerts: boolean status: 'active' | 'frozen' | 'closed' + totalTrades: number + totalVolume: string + tradeConfirmations: boolean } export type CryptoBootstrap = { diff --git a/frontend/src/views/apps/CryptoApp.contract.test.ts b/frontend/src/views/apps/CryptoApp.contract.test.ts index 9ab8bb9..bb44bd3 100644 --- a/frontend/src/views/apps/CryptoApp.contract.test.ts +++ b/frontend/src/views/apps/CryptoApp.contract.test.ts @@ -36,6 +36,15 @@ describe('VaultX crypto app contracts', () => { expect(source).toContain(' { + expect(source).toContain('detail.sparkline') + expect(source).toContain("t('marketDetail.statistics')") + expect(source).toContain(' { @@ -71,7 +80,7 @@ describe('VaultX crypto app contracts', () => { expect(passwordProvider).toContain('scryptSync') expect(passwordProvider).toContain('timingSafeEqual') expect(passwordProvider).toContain('randomBytes(16)') - expect(server).not.toContain('data.price') - expect(server).not.toContain('data.fee') + expect(server).not.toMatch(/data\.price\b/) + expect(server).not.toMatch(/data\.fee\b/) }) }) diff --git a/frontend/src/views/apps/CryptoApp.vue b/frontend/src/views/apps/CryptoApp.vue index e654a27..80bfb6c 100644 --- a/frontend/src/views/apps/CryptoApp.vue +++ b/frontend/src/views/apps/CryptoApp.vue @@ -1,22 +1,27 @@