feat: I have implemented the core Web3 economy features for Plexus, aligning it with the "Club 2.0" vision.

This commit is contained in:
2026-01-15 21:38:02 +01:00
parent 712f62f7ae
commit 64060f6a01
10 changed files with 1819 additions and 9 deletions

1509
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,11 +21,14 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.21.1",
"jsdom": "^27.4.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.17",
"vite": "^7.2.4"
"vite": "^7.2.4",
"vitest": "^4.0.17"
}
}
}

View File

@@ -174,7 +174,7 @@ const saveSettings = () => {
{{ username }}
</div>
<div class="text-[10px] text-gray-400 truncate">
#{{ walletAddress?.slice(-4) }}
#{{ walletAddress?.slice(-4) }} <span class="text-yellow-400">{{ chatStore.balance }} $PLEXUS</span>
</div>
</div>
<Settings

View File

@@ -0,0 +1,97 @@
// @vitest-environment jsdom
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { useChatStore } from '../chat';
// Mock socket.io-client
const mockSocket = {
on: vi.fn(),
emit: vi.fn(),
connected: true
};
vi.mock('socket.io-client', () => ({
io: () => mockSocket
}));
// Mock js-cookie
vi.mock('js-cookie', () => ({
default: {
set: vi.fn(),
get: vi.fn(),
remove: vi.fn()
}
}));
describe('Chat Store Web3 Economy', () => {
beforeEach(() => {
setActivePinia(createPinia());
vi.clearAllMocks();
// Reset socket mocks
mockSocket.on.mockReset();
mockSocket.emit.mockReset();
// Mock fetch
global.fetch = vi.fn(() => Promise.resolve({
json: () => Promise.resolve([])
}));
});
it('should initialize with default balance of 100', () => {
const store = useChatStore();
expect(store.balance).toBe(100);
});
it('should deduct 1 PLEXUS when sending a message', () => {
const store = useChatStore();
store.connect('wallet123', 'user123');
// Mock socket connection
const connectCallback = mockSocket.on.mock.calls.find(call => call[0] === 'connect')[1];
connectCallback();
const initialBalance = store.balance;
store.sendMessage('Hello');
expect(store.balance).toBe(initialBalance - 1);
});
it('should prevent sending message if balance is insufficient', () => {
const store = useChatStore();
store.connect('wallet123', 'user123');
store.balance = 0;
// Mock alert
window.alert = vi.fn();
store.sendMessage('Hello');
expect(store.balance).toBe(0);
expect(mockSocket.emit).not.toHaveBeenCalledWith('sendMessage', expect.anything());
expect(window.alert).toHaveBeenCalled();
});
it('should update balance when balanceUpdated event is received', () => {
const store = useChatStore();
store.connect('wallet123', 'user123');
// Find the balanceUpdated handler
// We need to trigger the socket.on call that registers the handler
// The store calls socket.on multiple times. We need to find the one for 'balanceUpdated'
// Since we mocked socket.on, we can simulate the event
// But the store registers listeners inside `connect`
// Get all calls to socket.on
const calls = mockSocket.on.mock.calls;
const balanceHandler = calls.find(call => call[0] === 'balanceUpdated')[1];
expect(balanceHandler).toBeDefined();
// Simulate event
balanceHandler({ balance: 50 });
expect(store.balance).toBe(50);
});
});

View File

@@ -9,6 +9,7 @@ export const useChatStore = defineStore('chat', () => {
const walletAddress = ref(null);
const username = ref(null);
const signature = ref(null);
const balance = ref(100); // Mock initial balance
const currentChannel = ref('nebula');
const messages = ref({}); // { channelId: [messages] }
@@ -105,6 +106,10 @@ export const useChatStore = defineStore('chat', () => {
}
});
socket.value.on('balanceUpdated', ({ balance: newBalance }) => {
balance.value = newBalance;
});
socket.value.on('error', (err) => {
console.error('Socket error:', err);
// Handle failed messages if we can identify them
@@ -119,6 +124,14 @@ export const useChatStore = defineStore('chat', () => {
function sendMessage(content) {
if (!socket.value || !content.trim()) return;
if (balance.value < 1) {
alert('Insufficient $PLEXUS balance! You need 1 $PLEXUS to send a message.');
return;
}
// Deduct token immediately for UI feedback
balance.value -= 1;
const tempId = 'temp-' + Date.now();
const pendingMsg = {
tempId,
@@ -263,6 +276,8 @@ export const useChatStore = defineStore('chat', () => {
getProfile,
updateProfile,
createPost,
setChannel
createPost,
setChannel,
balance
};
});