@meddleware/treasury-ui 0.0.1 → 0.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/treasury-ui",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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.6",
44
- "@meddleware/ui": "^0.1.27",
45
- "@meddleware/wallet-adapter": "^0.0.8",
47
+ "@meddleware/design-tokens": "^0.1.7",
48
+ "@meddleware/ui": "^0.1.28",
49
+ "@meddleware/wallet-adapter": "^0.0.9",
46
50
  "@mysten/sui": "^2.33.0",
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 style="flex: 1; min-height: 0" />
25
+ <TreasuryView />
26
26
 
27
27
  <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL">
28
28
  <template #start>
@@ -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, UiToolbarButton } from '@meddleware/ui'
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
- import type { Tab } from './components/TabBar.vue'
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="dao-view">
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
- <TabBar :tabs="TABS" v-model="activeTab" />
48
+ <AppTabNav v-model="activeTab" :tabs="TABS" id-prefix="treasury" variant="raised" aria-label="Treasury sections" />
54
49
 
55
- <div class="dao-content">
50
+ <UiTabPanel id-prefix="treasury" :tab="activeTab" class="dao-content">
56
51
  <component :is="activeComponent" />
57
- </div>
52
+ </UiTabPanel>
58
53
 
59
- <StatusBar :epoch="epoch" :last-refresh="lastRefresh" :error="!!eventsError" />
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: any): Promise<Gate | null> => {
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/styles/qt.css CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  /* ── Shell layout ────────────────────────────────────────────────────────── */
14
14
 
15
- .dao-view {
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 (max-width: 640px) {
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-progress__fill {
70
- height: 100%;
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;
@@ -32,12 +32,12 @@ function formatPrice(mist: bigint): string {
32
32
  </script>
33
33
 
34
34
  <template>
35
- <div style="display: flex; flex-direction: column; gap: 10px">
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 style="max-width: 480px">
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" style="font-size: 0.7rem; white-space: nowrap">
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>
@@ -39,56 +39,69 @@ function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
39
39
  </script>
40
40
 
41
41
  <template>
42
- <div>
43
- <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px">
44
- <p class="dao-section-title" style="margin: 0">Revenue Events</p>
45
- <UiToolbarButton :disabled="loading" @click="reload">
46
- {{ loading ? 'Loading…' : 'Refresh' }}
47
- </UiToolbarButton>
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
- <p v-if="error" class="dao-muted">{{ error }}</p>
49
+ <p v-if="error" class="dao-muted">{{ error }}</p>
51
50
 
52
- <UiDataTable v-if="pageEvents.length || loading" :empty="'No events'">
53
- <template #head>
54
- <th>Type</th>
55
- <th>
56
- Address
57
- <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>
58
- </th>
59
- <th>
60
- Block
61
- <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>
62
- </th>
63
- <th>
64
- Transaction
65
- <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>
66
- </th>
67
- </template>
68
- <tr v-for="ev in pageEvents" :key="ev.txDigest">
69
- <td><UiBadge :variant="badgeVariant(ev.type)">{{ eventLabel[ev.type] }}</UiBadge></td>
70
- <td class="dao-mono" style="font-size: 0.72rem; white-space: nowrap">
71
- <CopyableAddress v-if="ev.address && ev.address !== 'undefined'" :address="ev.address">
72
- <ExplorerLink :href="suiExplorerUrl('account', ev.address, NETWORK)" :value="ev.address" />
73
- </CopyableAddress>
74
- <span v-else>—</span>
75
- </td>
76
- <td class="dao-mono" style="font-size: 0.72rem">
77
- {{ ev.checkpoint ? '#' + ev.checkpoint : '—' }}
78
- </td>
79
- <td class="dao-mono" style="font-size: 0.72rem; white-space: nowrap">
80
- <CopyableAddress :address="ev.txDigest" label="Copy transaction">
81
- <ExplorerLink :href="suiExplorerUrl('txblock', ev.txDigest, NETWORK)" :value="ev.txDigest" />
82
- </CopyableAddress>
83
- </td>
84
- </tr>
85
- </UiDataTable>
86
- <p v-else-if="!loading" class="dao-placeholder">No events found.</p>
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
- <div v-if="totalPages > 1" class="dao-pagination">
89
- <UiToolbarButton :disabled="page === 1" @click="prevPage">← Prev</UiToolbarButton>
90
- <span class="dao-muted" style="font-size: 0.78rem">Page {{ page }} / {{ totalPages }}</span>
91
- <UiToolbarButton :disabled="page === totalPages" @click="nextPage">Next →</UiToolbarButton>
92
- </div>
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>
@@ -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 style="display: flex; flex-direction: column; gap: 8px">
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" style="font-size: 0.7rem">—</span>
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" style="margin: 0">Loading events…</p>
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"
@@ -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>