@meddleware/treasury-ui 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +29 -7
- package/src/App.vue +1 -1
- package/src/TreasuryView.vue +9 -14
- package/src/composables/useGates.ts +1 -1
- package/src/config.ts +2 -2
- package/src/styles/qt.css +46 -5
- package/src/tabs/AccountsTab.vue +3 -3
- package/src/tabs/ActivityTab.vue +62 -49
- package/src/tabs/OverviewTab.vue +3 -3
- package/vite.config.ts +5 -0
- package/src/components/StatusBar.vue +0 -37
- package/src/components/TabBar.vue +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/treasury-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"homepage": "https://docs.meddleware.co.uk/treasury/",
|
|
5
5
|
"description": "Meddleware treasury console — organisation-level accounting across revenue streams (Sui and beyond). Read-only, desktop-console UI built on @meddleware/ui.",
|
|
6
6
|
"author": "Meddleware <dev@meddleware.co.uk>",
|
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
"preview": "vite preview",
|
|
24
24
|
"type-check": "vue-tsc --noEmit",
|
|
25
25
|
"test": "vitest run",
|
|
26
|
-
"test:watch": "vitest"
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"lint:css": "stylelint \"src/**/*.{css,vue}\"",
|
|
28
|
+
"lint:js": "eslint .",
|
|
29
|
+
"lint": "npm run lint:css && npm run lint:js && npm run lint:html",
|
|
30
|
+
"lint:html": "html-validate \"src/**/*.vue\""
|
|
27
31
|
},
|
|
28
32
|
"exports": {
|
|
29
33
|
".": {
|
|
@@ -40,23 +44,41 @@
|
|
|
40
44
|
"access": "public"
|
|
41
45
|
},
|
|
42
46
|
"dependencies": {
|
|
43
|
-
"@meddleware/design-tokens": "^0.1.
|
|
44
|
-
"@meddleware/ui": "^0.1.
|
|
45
|
-
"@meddleware/wallet-adapter": "^0.0.
|
|
46
|
-
"@mysten/sui": "^2.33.
|
|
47
|
+
"@meddleware/design-tokens": "^0.1.7",
|
|
48
|
+
"@meddleware/ui": "^0.1.28",
|
|
49
|
+
"@meddleware/wallet-adapter": "^0.0.10",
|
|
50
|
+
"@mysten/sui": "^2.33.1",
|
|
47
51
|
"vue": "^3.5.43"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@types/node": "~24.12.2",
|
|
51
55
|
"@vitejs/plugin-vue": "^6.0.9",
|
|
56
|
+
"@vue/eslint-config-typescript": "^14.9.0",
|
|
52
57
|
"@vue/test-utils": "^2.5.1",
|
|
58
|
+
"autoprefixer": "^10.4.20",
|
|
59
|
+
"eslint": "^10.11.0",
|
|
60
|
+
"eslint-plugin-vue": "^10.11.1",
|
|
61
|
+
"eslint-plugin-vuejs-accessibility": "^2.6.0",
|
|
62
|
+
"html-validate": "^11.16.1",
|
|
63
|
+
"html-validate-vue": "^9.3.1",
|
|
64
|
+
"jiti": "^2.7.0",
|
|
53
65
|
"jsdom": "^29.1.1",
|
|
66
|
+
"postcss": "^8.4.49",
|
|
67
|
+
"postcss-html": "^2.0.0",
|
|
68
|
+
"stylelint": "^17.15.0",
|
|
69
|
+
"stylelint-config-standard": "^40.0.0",
|
|
54
70
|
"typescript": "~6.0.0",
|
|
55
71
|
"vite": "^8.3.0",
|
|
56
72
|
"vitest": "~5.0.1",
|
|
73
|
+
"vue-eslint-parser": "^10.4.1",
|
|
57
74
|
"vue-tsc": "~3.3.11"
|
|
58
75
|
},
|
|
59
76
|
"engines": {
|
|
60
77
|
"node": "^22.18.0 || >=24.12.0"
|
|
61
|
-
}
|
|
78
|
+
},
|
|
79
|
+
"browserslist": [
|
|
80
|
+
"> 0.5%",
|
|
81
|
+
"last 2 versions",
|
|
82
|
+
"not dead"
|
|
83
|
+
]
|
|
62
84
|
}
|
package/src/App.vue
CHANGED
|
@@ -22,7 +22,7 @@ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/tr
|
|
|
22
22
|
</template>
|
|
23
23
|
</AppHeader>
|
|
24
24
|
|
|
25
|
-
<TreasuryView
|
|
25
|
+
<TreasuryView />
|
|
26
26
|
|
|
27
27
|
<AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL">
|
|
28
28
|
<template #start>
|
package/src/TreasuryView.vue
CHANGED
|
@@ -11,18 +11,15 @@
|
|
|
11
11
|
// view is consumed as a library by the dashboard or any other host.
|
|
12
12
|
import './styles/qt.css'
|
|
13
13
|
import { ref, computed } from 'vue'
|
|
14
|
-
import { UiToolbar,
|
|
15
|
-
import TabBar from './components/TabBar.vue'
|
|
16
|
-
import StatusBar from './components/StatusBar.vue'
|
|
14
|
+
import { AppTabNav, UiStatusBar, UiTabPanel, UiToolbar, type AppTab } from '@meddleware/ui'
|
|
17
15
|
import OverviewTab from './tabs/OverviewTab.vue'
|
|
18
16
|
import AccountsTab from './tabs/AccountsTab.vue'
|
|
19
17
|
import ActivityTab from './tabs/ActivityTab.vue'
|
|
20
18
|
import { useTreasuryActivity } from './composables/useTreasuryActivity.js'
|
|
21
19
|
import { useEpoch } from './composables/useEpoch.js'
|
|
20
|
+
import { NETWORK } from './config.js'
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const TABS: Tab[] = [
|
|
22
|
+
const TABS: AppTab[] = [
|
|
26
23
|
{ id: 'overview', label: 'Overview' },
|
|
27
24
|
{ id: 'accounts', label: 'Accounts' },
|
|
28
25
|
{ id: 'activity', label: 'Activity' },
|
|
@@ -45,17 +42,15 @@ const { epoch } = useEpoch()
|
|
|
45
42
|
</script>
|
|
46
43
|
|
|
47
44
|
<template>
|
|
48
|
-
<div class="
|
|
49
|
-
<UiToolbar
|
|
50
|
-
<UiToolbarButton @click="activeTab = 'overview'">🏦 Meddleware Treasury</UiToolbarButton>
|
|
51
|
-
</UiToolbar>
|
|
45
|
+
<div class="treasury-view">
|
|
46
|
+
<UiToolbar :actions="[{ id: 'home', label: '🏦 Meddleware Treasury' }]" @action="activeTab = 'overview'" />
|
|
52
47
|
|
|
53
|
-
<
|
|
48
|
+
<AppTabNav v-model="activeTab" :tabs="TABS" id-prefix="treasury" variant="raised" aria-label="Treasury sections" />
|
|
54
49
|
|
|
55
|
-
<
|
|
50
|
+
<UiTabPanel id-prefix="treasury" :tab="activeTab" class="dao-content">
|
|
56
51
|
<component :is="activeComponent" />
|
|
57
|
-
</
|
|
52
|
+
</UiTabPanel>
|
|
58
53
|
|
|
59
|
-
<
|
|
54
|
+
<UiStatusBar :network="NETWORK" :healthy="!eventsError" :epoch="epoch" :last-refresh="lastRefresh" />
|
|
60
55
|
</div>
|
|
61
56
|
</template>
|
|
@@ -42,7 +42,7 @@ export function useGates() {
|
|
|
42
42
|
|
|
43
43
|
// Fetch each Gate shared object referenced by the AdminCap's gate_id.
|
|
44
44
|
const gateList = await Promise.all(
|
|
45
|
-
(objects ?? []).map(async (o:
|
|
45
|
+
(objects ?? []).map(async (o: { json?: { gate_id?: unknown } | null; object?: { json?: { gate_id?: unknown } | null } | null }): Promise<Gate | null> => {
|
|
46
46
|
const capFields = o?.json ?? o?.object?.json
|
|
47
47
|
const gateId = String(capFields?.gate_id ?? '')
|
|
48
48
|
if (!gateId || gateId === 'undefined') return null
|
package/src/config.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const RPC_URLS: Record<Network, string> = {
|
|
|
14
14
|
export const ACCESS_GATE_PACKAGE_ID: Record<Network, string> = {
|
|
15
15
|
testnet:
|
|
16
16
|
import.meta.env.VITE_ACCESS_GATE_PACKAGE_ID_TESTNET ||
|
|
17
|
-
'
|
|
17
|
+
'0x1a81ca177db039585e575beeeee4759466e55910e936a6733e38dbb65025eea4',
|
|
18
18
|
mainnet: import.meta.env.VITE_ACCESS_GATE_PACKAGE_ID_MAINNET || '',
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ export const ACCESS_GATE_PACKAGE_ID: Record<Network, string> = {
|
|
|
22
22
|
export const PLATFORM_CONFIG_ID: Record<Network, string> = {
|
|
23
23
|
testnet:
|
|
24
24
|
import.meta.env.VITE_PLATFORM_CONFIG_ID_TESTNET ||
|
|
25
|
-
'
|
|
25
|
+
'0xe3b949cabe9a0574c03dfc924fb3f96e6f959f2bb86d053ed6229a241c3a23f7',
|
|
26
26
|
mainnet: import.meta.env.VITE_PLATFORM_CONFIG_ID_MAINNET || '',
|
|
27
27
|
}
|
|
28
28
|
|
package/src/styles/qt.css
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
/* ── Shell layout ────────────────────────────────────────────────────────── */
|
|
14
14
|
|
|
15
|
-
.
|
|
15
|
+
.treasury-view {
|
|
16
16
|
display: flex;
|
|
17
17
|
flex-direction: column;
|
|
18
18
|
min-height: 0;
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
gap: 10px;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
@media (
|
|
52
|
+
@media (width <= 640px) {
|
|
53
53
|
.dao-overview-cols {
|
|
54
54
|
grid-template-columns: 1fr;
|
|
55
55
|
}
|
|
@@ -57,21 +57,32 @@
|
|
|
57
57
|
|
|
58
58
|
/* ── Proposal progress bar ───────────────────────────────────────────────── */
|
|
59
59
|
|
|
60
|
+
/* A native <progress>: engines paint the track/value with their own pseudo-elements. */
|
|
60
61
|
.dao-progress {
|
|
62
|
+
display: block;
|
|
63
|
+
width: 100%;
|
|
61
64
|
height: 6px;
|
|
65
|
+
margin: 3px 0;
|
|
66
|
+
appearance: none;
|
|
62
67
|
background: var(--lift);
|
|
63
68
|
border: 1px solid var(--border);
|
|
64
69
|
border-radius: 1px;
|
|
65
70
|
overflow: hidden;
|
|
66
|
-
margin: 3px 0;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
.dao-
|
|
70
|
-
|
|
73
|
+
.dao-progress::-webkit-progress-bar {
|
|
74
|
+
background: var(--lift);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dao-progress::-webkit-progress-value {
|
|
71
78
|
background: var(--accent);
|
|
72
79
|
transition: width 0.3s ease;
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
.dao-progress::-moz-progress-bar {
|
|
83
|
+
background: var(--accent);
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
/* ── Info tooltip marker ─────────────────────────────────────────────────── */
|
|
76
87
|
|
|
77
88
|
.dao-info {
|
|
@@ -102,6 +113,24 @@
|
|
|
102
113
|
margin-top: 10px;
|
|
103
114
|
}
|
|
104
115
|
|
|
116
|
+
/* ── Layout utilities ────────────────────────────────────────────────────── */
|
|
117
|
+
|
|
118
|
+
/* Vertical stack of panels within a tab. */
|
|
119
|
+
.dao-stack {
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
gap: 10px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.dao-stack--tight {
|
|
126
|
+
gap: 8px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Key/value readouts stay compact rather than spanning a wide panel. */
|
|
130
|
+
.dao-stat-grid {
|
|
131
|
+
max-width: 480px;
|
|
132
|
+
}
|
|
133
|
+
|
|
105
134
|
/* ── Misc text utilities ─────────────────────────────────────────────────── */
|
|
106
135
|
|
|
107
136
|
.dao-mono {
|
|
@@ -109,6 +138,18 @@
|
|
|
109
138
|
font-size: 0.8rem;
|
|
110
139
|
}
|
|
111
140
|
|
|
141
|
+
.dao-mono--sm {
|
|
142
|
+
font-size: 0.7rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.dao-nowrap {
|
|
146
|
+
white-space: nowrap;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.dao-flush {
|
|
150
|
+
margin: 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
112
153
|
.dao-muted {
|
|
113
154
|
color: var(--muted);
|
|
114
155
|
font-size: 0.78rem;
|
package/src/tabs/AccountsTab.vue
CHANGED
|
@@ -32,12 +32,12 @@ function formatPrice(mist: bigint): string {
|
|
|
32
32
|
</script>
|
|
33
33
|
|
|
34
34
|
<template>
|
|
35
|
-
<div
|
|
35
|
+
<div class="dao-stack">
|
|
36
36
|
<UiPanel title="Sui Treasury Account">
|
|
37
37
|
<p v-if="cfgLoading" class="dao-muted">Loading…</p>
|
|
38
38
|
<p v-else-if="cfgErr" class="dao-muted">{{ cfgErr }}</p>
|
|
39
39
|
<template v-else-if="config">
|
|
40
|
-
<UiStatGrid
|
|
40
|
+
<UiStatGrid class="dao-stat-grid">
|
|
41
41
|
<UiStatRow label="Commission rate">{{ commissionPct }} ({{ config.commissionBps }} bps)</UiStatRow>
|
|
42
42
|
<UiStatRow label="Current balance"><AmountCell :mist="balance" /></UiStatRow>
|
|
43
43
|
<UiStatRow label="Treasury address" align="left">
|
|
@@ -61,7 +61,7 @@ function formatPrice(mist: bigint): string {
|
|
|
61
61
|
<tr v-for="gate in gates" :key="gate.id">
|
|
62
62
|
<td>{{ gate.name }}</td>
|
|
63
63
|
<td class="dao-amount">{{ formatPrice(gate.price) }}</td>
|
|
64
|
-
<td class="dao-mono
|
|
64
|
+
<td class="dao-mono dao-mono--sm dao-nowrap">
|
|
65
65
|
<CopyableAddress :address="gate.id" label="Copy object ID">
|
|
66
66
|
<ExplorerLink :href="suiExplorerUrl('object', gate.id, NETWORK)" :value="gate.id" />
|
|
67
67
|
</CopyableAddress>
|
package/src/tabs/ActivityTab.vue
CHANGED
|
@@ -39,56 +39,69 @@ function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
|
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
41
|
<template>
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</div>
|
|
42
|
+
<header class="activity__head">
|
|
43
|
+
<h2 class="dao-section-title">Revenue Events</h2>
|
|
44
|
+
<UiToolbarButton :disabled="loading" @click="reload">
|
|
45
|
+
{{ loading ? 'Loading…' : 'Refresh' }}
|
|
46
|
+
</UiToolbarButton>
|
|
47
|
+
</header>
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
<p v-if="error" class="dao-muted">{{ error }}</p>
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
51
|
+
<UiDataTable v-if="pageEvents.length || loading" :empty="'No events'">
|
|
52
|
+
<template #head>
|
|
53
|
+
<th>Type</th>
|
|
54
|
+
<th>
|
|
55
|
+
Address
|
|
56
|
+
<abbr class="dao-info" title="The wallet address associated with this event (recipient, creator, or initiator depending on event type). Click to view in explorer; click the copy icon to copy the full address.">?</abbr>
|
|
57
|
+
</th>
|
|
58
|
+
<th>
|
|
59
|
+
Block
|
|
60
|
+
<abbr class="dao-info" title="Sui checkpoint sequence number — equivalent to a block height in other blockchains. Each checkpoint finalizes a batch of transactions; higher numbers are more recent.">?</abbr>
|
|
61
|
+
</th>
|
|
62
|
+
<th>
|
|
63
|
+
Transaction
|
|
64
|
+
<abbr class="dao-info" title="Transaction digest — a unique identifier for the transaction that emitted this event. Click to view in explorer; click the copy icon to copy the full digest.">?</abbr>
|
|
65
|
+
</th>
|
|
66
|
+
</template>
|
|
67
|
+
<tr v-for="ev in pageEvents" :key="ev.txDigest">
|
|
68
|
+
<td><UiBadge :variant="badgeVariant(ev.type)">{{ eventLabel[ev.type] }}</UiBadge></td>
|
|
69
|
+
<td class="dao-mono activity__cell dao-nowrap">
|
|
70
|
+
<CopyableAddress v-if="ev.address && ev.address !== 'undefined'" :address="ev.address">
|
|
71
|
+
<ExplorerLink :href="suiExplorerUrl('account', ev.address, NETWORK)" :value="ev.address" />
|
|
72
|
+
</CopyableAddress>
|
|
73
|
+
<span v-else>—</span>
|
|
74
|
+
</td>
|
|
75
|
+
<td class="dao-mono activity__cell">
|
|
76
|
+
{{ ev.checkpoint ? '#' + ev.checkpoint : '—' }}
|
|
77
|
+
</td>
|
|
78
|
+
<td class="dao-mono activity__cell dao-nowrap">
|
|
79
|
+
<CopyableAddress :address="ev.txDigest" label="Copy transaction">
|
|
80
|
+
<ExplorerLink :href="suiExplorerUrl('txblock', ev.txDigest, NETWORK)" :value="ev.txDigest" />
|
|
81
|
+
</CopyableAddress>
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
</UiDataTable>
|
|
85
|
+
<p v-else-if="!loading" class="dao-placeholder">No events found.</p>
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
</div>
|
|
87
|
+
<nav v-if="totalPages > 1" class="dao-pagination" aria-label="Event pages">
|
|
88
|
+
<UiToolbarButton :disabled="page === 1" @click="prevPage">← Prev</UiToolbarButton>
|
|
89
|
+
<span class="dao-muted">Page {{ page }} / {{ totalPages }}</span>
|
|
90
|
+
<UiToolbarButton :disabled="page === totalPages" @click="nextPage">Next →</UiToolbarButton>
|
|
91
|
+
</nav>
|
|
94
92
|
</template>
|
|
93
|
+
|
|
94
|
+
<style scoped>
|
|
95
|
+
.activity__head {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: space-between;
|
|
99
|
+
margin-bottom: 8px;
|
|
100
|
+
}
|
|
101
|
+
.activity__head .dao-section-title {
|
|
102
|
+
margin: 0;
|
|
103
|
+
}
|
|
104
|
+
.activity__cell {
|
|
105
|
+
font-size: 0.72rem;
|
|
106
|
+
}
|
|
107
|
+
</style>
|
package/src/tabs/OverviewTab.vue
CHANGED
|
@@ -42,7 +42,7 @@ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'A
|
|
|
42
42
|
<template>
|
|
43
43
|
<div class="dao-overview-cols">
|
|
44
44
|
<!-- Left: treasury summary -->
|
|
45
|
-
<div
|
|
45
|
+
<div class="dao-stack dao-stack--tight">
|
|
46
46
|
<UiPanel title="Treasury Balance">
|
|
47
47
|
<UiStatGrid>
|
|
48
48
|
<UiStatRow label="SUI balance"><AmountCell :mist="balance" /></UiStatRow>
|
|
@@ -51,7 +51,7 @@ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'A
|
|
|
51
51
|
<CopyableAddress v-if="config?.treasury" :address="config.treasury">
|
|
52
52
|
<ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
|
|
53
53
|
</CopyableAddress>
|
|
54
|
-
<span v-else class="dao-mono
|
|
54
|
+
<span v-else class="dao-mono dao-mono--sm">—</span>
|
|
55
55
|
</UiStatRow>
|
|
56
56
|
</UiStatGrid>
|
|
57
57
|
</UiPanel>
|
|
@@ -67,7 +67,7 @@ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'A
|
|
|
67
67
|
|
|
68
68
|
<!-- Right: recent activity -->
|
|
69
69
|
<UiPanel title="Recent Activity">
|
|
70
|
-
<p v-if="eventsLoading" class="dao-muted
|
|
70
|
+
<p v-if="eventsLoading" class="dao-muted dao-flush">Loading events…</p>
|
|
71
71
|
<UiActivityFeed v-else-if="events.length">
|
|
72
72
|
<UiActivityItem
|
|
73
73
|
v-for="ev in events"
|
package/vite.config.ts
CHANGED
|
@@ -3,4 +3,9 @@ import vue from '@vitejs/plugin-vue'
|
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
plugins: [vue()],
|
|
6
|
+
optimizeDeps: {
|
|
7
|
+
// @meddleware/wallet-adapter ships TS + .vue source and holds the shared wallet singleton; if it
|
|
8
|
+
// were pre-bundled, its .vue files (served raw) would load a second copy of the singleton in dev.
|
|
9
|
+
exclude: ['@meddleware/wallet-adapter'],
|
|
10
|
+
},
|
|
6
11
|
})
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// Bottom status bar: network + health dot (error-driven), current epoch, and a
|
|
3
|
-
// relative "refreshed Ns ago" timestamp. Built on the shared UiStatusBar/UiStatusDot.
|
|
4
|
-
import { computed } from 'vue'
|
|
5
|
-
import { UiStatusBar, UiStatusDot } from '@meddleware/ui'
|
|
6
|
-
import { NETWORK } from '../config.js'
|
|
7
|
-
|
|
8
|
-
const props = defineProps<{
|
|
9
|
-
epoch: number | null
|
|
10
|
-
lastRefresh: Date | null
|
|
11
|
-
error?: boolean
|
|
12
|
-
}>()
|
|
13
|
-
|
|
14
|
-
const dotStatus = computed<'ok' | 'error'>(() => (props.error ? 'error' : 'ok'))
|
|
15
|
-
|
|
16
|
-
function timeAgo(d: Date): string {
|
|
17
|
-
const s = Math.floor((Date.now() - d.getTime()) / 1000)
|
|
18
|
-
if (s < 5) return 'just now'
|
|
19
|
-
if (s < 60) return `${s}s ago`
|
|
20
|
-
return `${Math.floor(s / 60)}m ago`
|
|
21
|
-
}
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<UiStatusBar>
|
|
26
|
-
<span class="mw-statusbar__item">
|
|
27
|
-
<UiStatusDot :status="dotStatus" />
|
|
28
|
-
{{ NETWORK.charAt(0).toUpperCase() + NETWORK.slice(1) }}
|
|
29
|
-
</span>
|
|
30
|
-
<span class="mw-statusbar__sep">│</span>
|
|
31
|
-
<span v-if="epoch !== null" class="mw-statusbar__item">Epoch {{ epoch }}</span>
|
|
32
|
-
<span v-else class="mw-statusbar__item dao-muted">Epoch —</span>
|
|
33
|
-
<span class="mw-statusbar__sep">│</span>
|
|
34
|
-
<span v-if="lastRefresh" class="mw-statusbar__item">Refreshed {{ timeAgo(lastRefresh) }}</span>
|
|
35
|
-
<span v-else class="mw-statusbar__item dao-muted">Loading…</span>
|
|
36
|
-
</UiStatusBar>
|
|
37
|
-
</template>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// Thin wrapper over AppTabNav variant="raised" from @meddleware/ui.
|
|
3
|
-
// Keeps the app's internal import path stable while using the shared component.
|
|
4
|
-
import { AppTabNav } from '@meddleware/ui'
|
|
5
|
-
export type { AppTab as Tab } from '@meddleware/ui'
|
|
6
|
-
|
|
7
|
-
defineProps<{ tabs: { id: string; label: string }[]; modelValue: string }>()
|
|
8
|
-
const emit = defineEmits<{ 'update:modelValue': [id: string] }>()
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<AppTabNav
|
|
13
|
-
:tabs="tabs"
|
|
14
|
-
:model-value="modelValue"
|
|
15
|
-
variant="raised"
|
|
16
|
-
aria-label="Treasury sections"
|
|
17
|
-
@update:model-value="emit('update:modelValue', $event)"
|
|
18
|
-
/>
|
|
19
|
-
</template>
|