@meddleware/dao-ui 0.1.18 → 0.1.19

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/dao-ui",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "homepage": "https://docs.meddleware.co.uk/blockchain/sui/dao/",
5
5
  "description": "Meddleware DAO console — treasury overview, proposals, and governance on Sui.",
6
6
  "author": "Meddleware <dev@meddleware.co.uk>",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@meddleware/design-tokens": "^0.1.6",
44
- "@meddleware/ui": "^0.1.26",
44
+ "@meddleware/ui": "^0.1.27",
45
45
  "@meddleware/wallet-adapter": "^0.0.8",
46
46
  "@mysten/sui": "^2.31.0",
47
47
  "vue": "^3.5.43"
package/src/App.vue CHANGED
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  // Standalone shell for the DAO SPA. The core UI lives in DaoView.vue (also exported
3
3
  // for inline embedding in the dashboard).
4
- import { AppHeader, AppFooter, ColorModeControl, useColorMode } from '@meddleware/ui'
4
+ import { AppHeader, AppFooter, ColorModeControl, CopyrightLine, useColorMode } from '@meddleware/ui'
5
5
  import { NETWORK } from './config.js'
6
6
  import DaoView from './DaoView.vue'
7
7
 
@@ -24,7 +24,11 @@ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/
24
24
 
25
25
  <DaoView style="flex: 1; min-height: 0" />
26
26
 
27
- <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL" />
27
+ <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL">
28
+ <template #start>
29
+ <CopyrightLine symbol-variant="kopimi" organisation-name="Meddleware" rights-statement="jam" />
30
+ </template>
31
+ </AppFooter>
28
32
  </div>
29
33
  </template>
30
34
 
package/src/DaoView.vue CHANGED
@@ -7,6 +7,7 @@
7
7
  // DaoView is consumed as a library by the dashboard or any other host.
8
8
  import './styles/qt.css'
9
9
  import { ref, computed } from 'vue'
10
+ import { UiToolbar, UiToolbarButton } from '@meddleware/ui'
10
11
  import TabBar from './components/TabBar.vue'
11
12
  import StatusBar from './components/StatusBar.vue'
12
13
  import OverviewTab from './tabs/OverviewTab.vue'
@@ -45,9 +46,9 @@ const { epoch } = useEpoch()
45
46
 
46
47
  <template>
47
48
  <div class="dao-view">
48
- <div class="dao-toolbar">
49
- <button class="dao-toolbar__btn" @click="activeTab = 'overview'">🏛 Meddleware DAO</button>
50
- </div>
49
+ <UiToolbar>
50
+ <UiToolbarButton @click="activeTab = 'overview'">🏛 Meddleware DAO</UiToolbarButton>
51
+ </UiToolbar>
51
52
 
52
53
  <TabBar :tabs="TABS" v-model="activeTab" />
53
54
 
@@ -2,6 +2,7 @@
2
2
  // Renders one fundraising/governance proposal: status badge, progress toward target,
3
3
  // and epochs-left. The Contribute button is disabled until the vault_dao module ships.
4
4
  import { computed } from 'vue'
5
+ import { UiPanel, UiBadge, UiToolbarButton } from '@meddleware/ui'
5
6
  import type { Proposal } from '../composables/useProposals.js'
6
7
 
7
8
  const props = defineProps<{
@@ -19,10 +20,10 @@ const epochsLeft = computed(() => {
19
20
  return props.proposal.endEpoch - props.currentEpoch
20
21
  })
21
22
 
22
- const statusBadge = computed(() => {
23
- if (props.proposal.status === 'active') return 'dao-badge--active'
24
- if (props.proposal.status === 'pending') return 'dao-badge--pending'
25
- return 'dao-badge--closed'
23
+ const badgeVariant = computed<'active' | 'pending' | 'closed'>(() => {
24
+ if (props.proposal.status === 'active') return 'active'
25
+ if (props.proposal.status === 'pending') return 'pending'
26
+ return 'closed'
26
27
  })
27
28
 
28
29
  function formatSui(mist: bigint): string {
@@ -32,39 +33,36 @@ function formatSui(mist: bigint): string {
32
33
  </script>
33
34
 
34
35
  <template>
35
- <div class="dao-panel" style="margin-bottom: 8px">
36
- <div class="dao-panel__body">
37
- <div style="display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 6px">
38
- <div>
39
- <span :class="['dao-badge', statusBadge]" style="margin-right: 6px">{{ proposal.status }}</span>
40
- <strong style="font-size: 0.85rem">{{ proposal.title }}</strong>
41
- </div>
42
- <span class="dao-muted" style="white-space: nowrap; font-family: var(--font-mono); font-size: 0.75rem">
43
- <template v-if="epochsLeft !== null && epochsLeft > 0">{{ epochsLeft }} epochs left</template>
44
- <template v-else-if="epochsLeft !== null && epochsLeft <= 0">Expired</template>
45
- </span>
36
+ <UiPanel style="margin-bottom: 8px">
37
+ <div style="display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 6px">
38
+ <div>
39
+ <UiBadge :variant="badgeVariant" style="margin-right: 6px">{{ proposal.status }}</UiBadge>
40
+ <strong style="font-size: 0.85rem">{{ proposal.title }}</strong>
46
41
  </div>
42
+ <span class="dao-muted" style="white-space: nowrap; font-family: var(--mw-font-mono); font-size: 0.75rem">
43
+ <template v-if="epochsLeft !== null && epochsLeft > 0">{{ epochsLeft }} epochs left</template>
44
+ <template v-else-if="epochsLeft !== null && epochsLeft <= 0">Expired</template>
45
+ </span>
46
+ </div>
47
47
 
48
- <p class="dao-muted" style="margin: 0 0 8px; font-size: 0.78rem">{{ proposal.description }}</p>
48
+ <p class="dao-muted" style="margin: 0 0 8px; font-size: 0.78rem">{{ proposal.description }}</p>
49
49
 
50
- <div style="margin-bottom: 4px">
51
- <div class="dao-progress">
52
- <div class="dao-progress__fill" :style="{ width: `${pct}%` }" />
53
- </div>
54
- <div style="display: flex; justify-content: space-between; font-size: 0.72rem; color: var(--muted); font-family: var(--font-mono); margin-top: 2px">
55
- <span>{{ formatSui(proposal.contributedMist) }} SUI raised</span>
56
- <span>{{ pct }}% of {{ formatSui(proposal.targetMist) }} SUI target</span>
57
- </div>
50
+ <div style="margin-bottom: 4px">
51
+ <div class="dao-progress">
52
+ <div class="dao-progress__fill" :style="{ width: `${pct}%` }" />
53
+ </div>
54
+ <div style="display: flex; justify-content: space-between; font-size: 0.72rem; color: var(--muted); font-family: var(--mw-font-mono); margin-top: 2px">
55
+ <span>{{ formatSui(proposal.contributedMist) }} SUI raised</span>
56
+ <span>{{ pct }}% of {{ formatSui(proposal.targetMist) }} SUI target</span>
58
57
  </div>
59
-
60
- <button
61
- class="dao-toolbar__btn"
62
- disabled
63
- title="Vault DAO governance module launching soon"
64
- style="margin-top: 6px; opacity: 0.5; cursor: not-allowed"
65
- >
66
- Contribute
67
- </button>
68
58
  </div>
69
- </div>
59
+
60
+ <UiToolbarButton
61
+ disabled
62
+ title="Vault DAO governance module launching soon"
63
+ style="margin-top: 6px; opacity: 0.5; cursor: not-allowed"
64
+ >
65
+ Contribute
66
+ </UiToolbarButton>
67
+ </UiPanel>
70
68
  </template>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  // Bottom status bar: network + health dot (error-driven), current epoch, and a
3
- // relative "refreshed Ns ago" timestamp. Purely presentational.
3
+ // relative "refreshed Ns ago" timestamp. Built on the shared UiStatusBar/UiStatusDot.
4
4
  import { computed } from 'vue'
5
+ import { UiStatusBar, UiStatusDot } from '@meddleware/ui'
5
6
  import { NETWORK } from '../config.js'
6
7
 
7
8
  const props = defineProps<{
@@ -10,7 +11,7 @@ const props = defineProps<{
10
11
  error?: boolean
11
12
  }>()
12
13
 
13
- const dotClass = computed(() => (props.error ? 'dao-statusbar__dot--error' : 'dao-statusbar__dot--ok'))
14
+ const dotStatus = computed<'ok' | 'error'>(() => (props.error ? 'error' : 'ok'))
14
15
 
15
16
  function timeAgo(d: Date): string {
16
17
  const s = Math.floor((Date.now() - d.getTime()) / 1000)
@@ -21,16 +22,16 @@ function timeAgo(d: Date): string {
21
22
  </script>
22
23
 
23
24
  <template>
24
- <footer class="dao-statusbar">
25
- <span class="dao-statusbar__item">
26
- <span class="dao-statusbar__dot" :class="dotClass" />
25
+ <UiStatusBar>
26
+ <span class="mw-statusbar__item">
27
+ <UiStatusDot :status="dotStatus" />
27
28
  {{ NETWORK.charAt(0).toUpperCase() + NETWORK.slice(1) }}
28
29
  </span>
29
- <span class="dao-statusbar__sep">│</span>
30
- <span v-if="epoch !== null" class="dao-statusbar__item">Epoch {{ epoch }}</span>
31
- <span v-else class="dao-statusbar__item dao-muted">Epoch —</span>
32
- <span class="dao-statusbar__sep">│</span>
33
- <span v-if="lastRefresh" class="dao-statusbar__item">Refreshed {{ timeAgo(lastRefresh) }}</span>
34
- <span v-else class="dao-statusbar__item dao-muted">Loading…</span>
35
- </footer>
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>
36
37
  </template>
@@ -1,4 +1,4 @@
1
- import { ref, onMounted } from 'vue'
1
+ import { ref, onMounted, getCurrentInstance } from 'vue'
2
2
  import { getSuiClient } from '../wallet.js'
3
3
  import { PACKAGE_ID } from '../config.js'
4
4
 
@@ -68,7 +68,8 @@ export function useDaoEvents(limit = 20) {
68
68
  }
69
69
  }
70
70
 
71
- onMounted(load)
71
+ // Auto-load on mount, but only when used inside a component (skips in unit tests).
72
+ if (getCurrentInstance()) onMounted(load)
72
73
 
73
74
  return { events, loading, error, lastRefresh, reload: load }
74
75
  }
@@ -1,4 +1,4 @@
1
- import { ref, onMounted } from 'vue'
1
+ import { ref, onMounted, getCurrentInstance } from 'vue'
2
2
  import { getSuiClient } from '../wallet.js'
3
3
 
4
4
  export function useEpoch() {
@@ -14,7 +14,8 @@ export function useEpoch() {
14
14
  }
15
15
  }
16
16
 
17
- onMounted(load)
17
+ // Auto-load on mount, but only when used inside a component (skips in unit tests).
18
+ if (getCurrentInstance()) onMounted(load)
18
19
 
19
20
  return { epoch, reload: load }
20
21
  }
@@ -1,4 +1,4 @@
1
- import { ref, onMounted } from 'vue'
1
+ import { ref, onMounted, getCurrentInstance } from 'vue'
2
2
  import { getSuiClient } from '../wallet.js'
3
3
  import { PACKAGE_ID, CONFIG_ID } from '../config.js'
4
4
 
@@ -70,7 +70,8 @@ export function useGates() {
70
70
  }
71
71
  }
72
72
 
73
- onMounted(load)
73
+ // Auto-load on mount, but only when used inside a component (skips in unit tests).
74
+ if (getCurrentInstance()) onMounted(load)
74
75
 
75
76
  return { gates, loading, error, reload: load }
76
77
  }
@@ -1,7 +1,7 @@
1
1
  // Reads the PlatformConfig shared object once on mount and exposes the treasury
2
2
  // address + commission rate (bps). Source of truth for the commission model and for
3
3
  // whose AdminCaps the gate discovery (useGates) looks up.
4
- import { ref, onMounted } from 'vue'
4
+ import { ref, onMounted, getCurrentInstance } from 'vue'
5
5
  import { getSuiClient } from '../wallet.js'
6
6
  import { CONFIG_ID } from '../config.js'
7
7
 
@@ -35,7 +35,8 @@ export function usePlatformConfig() {
35
35
  }
36
36
  }
37
37
 
38
- onMounted(load)
38
+ // Auto-load on mount, but only when used inside a component (skips in unit tests).
39
+ if (getCurrentInstance()) onMounted(load)
39
40
 
40
41
  return { config, loading, error, reload: load }
41
42
  }
package/src/styles/qt.css CHANGED
@@ -1,11 +1,16 @@
1
1
  /*
2
- * Desktop-application (Bitcoin-Qt / Monero-Qt) aesthetic layer.
3
- * Layered on top of @meddleware/design-tokens — uses CSS vars from the token set.
4
- * Applied globally via main.ts; does not affect host documents when DaoView is embedded
5
- * because the overrides are scoped to .dao-view and its descendants.
2
+ * dao-ui console — app-local utility classes.
3
+ *
4
+ * The structural primitives (panel, toolbar, status bar, data table, stat grid,
5
+ * badge, activity feed) now come from @meddleware/ui as scoped components
6
+ * (UiPanel, UiToolbar, UiStatusBar, UiDataTable, UiStatGrid/UiStatRow, UiBadge,
7
+ * UiActivityFeed/UiActivityItem). What remains here are small text/layout
8
+ * utilities that are specific to this console's tabs.
9
+ *
10
+ * Applied globally via DaoView.vue (so it works when embedded as a library).
6
11
  */
7
12
 
8
- /* ── Layout shell ────────────────────────────────────────────────────────── */
13
+ /* ── Shell layout ────────────────────────────────────────────────────────── */
9
14
 
10
15
  .dao-view {
11
16
  display: flex;
@@ -16,48 +21,6 @@
16
21
  color: var(--text);
17
22
  }
18
23
 
19
- /* ── Toolbar ─────────────────────────────────────────────────────────────── */
20
-
21
- .dao-toolbar {
22
- display: flex;
23
- align-items: center;
24
- gap: 4px;
25
- padding: 4px 8px;
26
- border-bottom: 1px solid var(--border);
27
- background: var(--surface);
28
- flex-shrink: 0;
29
- }
30
-
31
- .dao-toolbar__btn {
32
- display: inline-flex;
33
- align-items: center;
34
- gap: 5px;
35
- padding: 3px 10px;
36
- height: 26px;
37
- font-size: 0.8rem;
38
- background: var(--lift);
39
- border: 1px solid var(--border);
40
- border-radius: 2px;
41
- color: var(--text);
42
- cursor: pointer;
43
- white-space: nowrap;
44
- transition: background 0.1s;
45
- }
46
-
47
- .dao-toolbar__btn:hover {
48
- background: var(--surface);
49
- border-color: var(--accent);
50
- }
51
-
52
- .dao-toolbar__sep {
53
- width: 1px;
54
- height: 18px;
55
- background: var(--border);
56
- margin: 0 4px;
57
- }
58
-
59
- /* ── Tab content area ────────────────────────────────────────────────────── */
60
-
61
24
  .dao-content {
62
25
  flex: 1;
63
26
  overflow-y: auto;
@@ -65,108 +28,10 @@
65
28
  background: var(--bg);
66
29
  }
67
30
 
68
- /* ── Status bar ──────────────────────────────────────────────────────────── */
69
-
70
- .dao-statusbar {
71
- display: flex;
72
- align-items: center;
73
- gap: 16px;
74
- padding: 3px 12px;
75
- font-size: 0.72rem;
76
- color: var(--muted);
77
- background: var(--surface);
78
- border-top: 1px solid var(--border);
79
- flex-shrink: 0;
80
- font-family: var(--font-mono);
81
- position: sticky;
82
- bottom: 0;
83
- z-index: 1;
84
- }
85
-
86
- .dao-statusbar__dot {
87
- display: inline-block;
88
- width: 7px;
89
- height: 7px;
90
- border-radius: 50%;
91
- margin-right: 4px;
92
- flex-shrink: 0;
93
- }
94
-
95
- .dao-statusbar__dot--ok { background: var(--ok); }
96
- .dao-statusbar__dot--warn { background: #b08d2a; }
97
- .dao-statusbar__dot--error { background: var(--danger); }
98
-
99
- .dao-statusbar__item {
100
- display: flex;
101
- align-items: center;
102
- }
103
-
104
- .dao-statusbar__sep {
105
- color: var(--border);
106
- }
107
-
108
- /* ── Panel ───────────────────────────────────────────────────────────────── */
109
-
110
- .dao-panel {
111
- border: 1px solid var(--border);
112
- border-radius: 2px;
113
- background: var(--surface);
114
- overflow: hidden;
115
- }
116
-
117
- .dao-panel__head {
118
- padding: 5px 10px;
119
- font-size: 0.72rem;
120
- font-weight: 600;
121
- letter-spacing: 0.06em;
122
- text-transform: uppercase;
123
- color: var(--muted);
124
- background: var(--lift);
125
- border-bottom: 1px solid var(--border);
126
- }
127
-
128
- .dao-panel__body {
129
- padding: 10px;
130
- }
131
-
132
- /* ── Data table ──────────────────────────────────────────────────────────── */
133
-
134
- .dao-table {
135
- width: 100%;
136
- border-collapse: collapse;
137
- font-size: 0.8rem;
138
- }
139
-
140
- .dao-table th {
141
- text-align: left;
142
- padding: 5px 8px;
143
- font-size: 0.7rem;
144
- font-weight: 600;
145
- letter-spacing: 0.05em;
146
- text-transform: uppercase;
147
- color: var(--muted);
148
- border-bottom: 1px solid var(--border);
149
- white-space: nowrap;
150
- }
151
-
152
- .dao-table td {
153
- padding: 5px 8px;
154
- border-bottom: 1px solid var(--border);
155
- vertical-align: middle;
156
- }
157
-
158
- .dao-table tr:last-child td {
159
- border-bottom: none;
160
- }
161
-
162
- .dao-table tr:nth-child(even) td {
163
- background: var(--lift);
164
- }
165
-
166
31
  /* ── Amount cells (monospace, right-aligned) ─────────────────────────────── */
167
32
 
168
33
  .dao-amount {
169
- font-family: var(--font-mono);
34
+ font-family: var(--mw-font-mono);
170
35
  font-size: 0.8rem;
171
36
  text-align: right;
172
37
  white-space: nowrap;
@@ -176,26 +41,6 @@
176
41
  color: var(--muted);
177
42
  }
178
43
 
179
- /* ── Stat grid (Overview key/value pairs) ────────────────────────────────── */
180
-
181
- .dao-stat-grid {
182
- display: grid;
183
- grid-template-columns: auto 1fr;
184
- gap: 3px 12px;
185
- font-size: 0.8rem;
186
- }
187
-
188
- .dao-stat-grid__label {
189
- color: var(--muted);
190
- white-space: nowrap;
191
- }
192
-
193
- .dao-stat-grid__value {
194
- font-family: var(--font-mono);
195
- color: var(--text);
196
- text-align: right;
197
- }
198
-
199
44
  /* ── Two-column overview layout ──────────────────────────────────────────── */
200
45
 
201
46
  .dao-overview-cols {
@@ -205,7 +50,9 @@
205
50
  }
206
51
 
207
52
  @media (max-width: 640px) {
208
- .dao-overview-cols { grid-template-columns: 1fr; }
53
+ .dao-overview-cols {
54
+ grid-template-columns: 1fr;
55
+ }
209
56
  }
210
57
 
211
58
  /* ── Proposal progress bar ───────────────────────────────────────────────── */
@@ -225,50 +72,6 @@
225
72
  transition: width 0.3s ease;
226
73
  }
227
74
 
228
- /* ── Activity feed ───────────────────────────────────────────────────────── */
229
-
230
- .dao-feed {
231
- list-style: none;
232
- padding: 0;
233
- margin: 0;
234
- font-size: 0.78rem;
235
- }
236
-
237
- .dao-feed__item {
238
- display: flex;
239
- align-items: flex-start;
240
- gap: 8px;
241
- padding: 4px 0;
242
- border-bottom: 1px solid var(--border);
243
- color: var(--muted);
244
- }
245
-
246
- .dao-feed__item:last-child {
247
- border-bottom: none;
248
- }
249
-
250
- .dao-feed__dot {
251
- width: 6px;
252
- height: 6px;
253
- border-radius: 50%;
254
- background: var(--accent);
255
- margin-top: 4px;
256
- flex-shrink: 0;
257
- }
258
-
259
- .dao-feed__type {
260
- color: var(--text);
261
- font-weight: 500;
262
- white-space: nowrap;
263
- }
264
-
265
- .dao-feed__time {
266
- margin-left: auto;
267
- white-space: nowrap;
268
- font-family: var(--font-mono);
269
- font-size: 0.72rem;
270
- }
271
-
272
75
  /* ── Info tooltip marker ─────────────────────────────────────────────────── */
273
76
 
274
77
  .dao-info {
@@ -299,10 +102,10 @@
299
102
  margin-top: 10px;
300
103
  }
301
104
 
302
- /* ── Misc ────────────────────────────────────────────────────────────────── */
105
+ /* ── Misc text utilities ─────────────────────────────────────────────────── */
303
106
 
304
107
  .dao-mono {
305
- font-family: var(--font-mono);
108
+ font-family: var(--mw-font-mono);
306
109
  font-size: 0.8rem;
307
110
  }
308
111
 
@@ -311,20 +114,6 @@
311
114
  font-size: 0.78rem;
312
115
  }
313
116
 
314
- .dao-badge {
315
- display: inline-block;
316
- padding: 1px 6px;
317
- border-radius: 2px;
318
- font-size: 0.7rem;
319
- font-weight: 600;
320
- letter-spacing: 0.04em;
321
- text-transform: uppercase;
322
- }
323
-
324
- .dao-badge--active { background: color-mix(in srgb, var(--ok) 20%, transparent); color: var(--ok); }
325
- .dao-badge--closed { background: color-mix(in srgb, var(--muted) 20%, transparent); color: var(--muted); }
326
- .dao-badge--pending { background: color-mix(in srgb, #b08d2a 20%, transparent); color: #b08d2a; }
327
-
328
117
  .dao-section-title {
329
118
  font-size: 0.72rem;
330
119
  font-weight: 600;
@@ -336,10 +125,10 @@
336
125
 
337
126
  .dao-notice {
338
127
  padding: 8px 10px;
339
- border: 1px solid #b08d2a;
128
+ border: 1px solid var(--warning);
340
129
  border-radius: 2px;
341
- background: color-mix(in srgb, #b08d2a 10%, transparent);
342
- color: #b08d2a;
130
+ background: color-mix(in srgb, var(--warning) 10%, transparent);
131
+ color: var(--warning);
343
132
  font-size: 0.78rem;
344
133
  margin-bottom: 10px;
345
134
  }
@@ -1,10 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, watch, computed } from 'vue'
3
- import Panel from '../components/Panel.vue'
4
3
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
5
4
  import { useWallet, getSuiClient } from '../wallet.js'
6
5
  import { PACKAGE_ID } from '../config.js'
7
- import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
6
+ import {
7
+ CopyableAddress,
8
+ ExplorerLink,
9
+ suiExplorerUrl,
10
+ UiPanel,
11
+ UiStatGrid,
12
+ UiStatRow,
13
+ } from '@meddleware/ui'
8
14
  import { NETWORK } from '../config.js'
9
15
 
10
16
  const { config } = usePlatformConfig()
@@ -45,22 +51,17 @@ watch(
45
51
 
46
52
  <template>
47
53
  <div style="display: flex; flex-direction: column; gap: 10px">
48
- <Panel title="Platform Parameters">
49
- <div class="dao-stat-grid" style="max-width: 480px">
50
- <span class="dao-stat-grid__label">Commission rate</span>
51
- <span class="dao-stat-grid__value dao-mono">{{ commissionPct }}</span>
52
-
53
- <span class="dao-stat-grid__label">Max commission cap</span>
54
- <span class="dao-stat-grid__value dao-mono">10.00% (1000 bps — on-chain hard cap)</span>
55
-
56
- <span class="dao-stat-grid__label">Treasury address</span>
57
- <span class="dao-stat-grid__value" style="text-align: left">
54
+ <UiPanel title="Platform Parameters">
55
+ <UiStatGrid style="max-width: 480px">
56
+ <UiStatRow label="Commission rate">{{ commissionPct }}</UiStatRow>
57
+ <UiStatRow label="Max commission cap">10.00% (1000 bps — on-chain hard cap)</UiStatRow>
58
+ <UiStatRow label="Treasury address" align="left">
58
59
  <CopyableAddress v-if="config?.treasury" :address="config.treasury">
59
60
  <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
60
61
  </CopyableAddress>
61
62
  <span v-else class="dao-mono" style="font-size: 0.7rem">—</span>
62
- </span>
63
- </div>
63
+ </UiStatRow>
64
+ </UiStatGrid>
64
65
 
65
66
  <p class="dao-muted" style="margin: 10px 0 0; font-size: 0.72rem">
66
67
  Parameters are governed by <code>PlatformAdminCap</code> on-chain.
@@ -68,9 +69,9 @@ watch(
68
69
  <code>commission_bps / 10000 × price</code> routes to treasury;
69
70
  the remainder goes to the gate operator.
70
71
  </p>
71
- </Panel>
72
+ </UiPanel>
72
73
 
73
- <Panel title="Admin Actions">
74
+ <UiPanel title="Admin Actions">
74
75
  <!-- Not connected -->
75
76
  <template v-if="!account">
76
77
  <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
@@ -109,6 +110,6 @@ watch(
109
110
  Unable to verify capabilities. Check your connection and try again.
110
111
  </p>
111
112
  </template>
112
- </Panel>
113
+ </UiPanel>
113
114
  </div>
114
115
  </template>
@@ -2,9 +2,15 @@
2
2
  // History tab: paginated on-chain access-gate event log (sold / used / burned) with
3
3
  // per-row explorer links. Data comes from useDaoEvents (pruning-tolerant, merged feed).
4
4
  import { ref, computed } from 'vue'
5
- import DataTable from '../components/DataTable.vue'
6
5
  import { useDaoEvents } from '../composables/useDaoEvents.js'
7
- import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
6
+ import {
7
+ CopyableAddress,
8
+ ExplorerLink,
9
+ suiExplorerUrl,
10
+ UiDataTable,
11
+ UiBadge,
12
+ UiToolbarButton,
13
+ } from '@meddleware/ui'
8
14
  import { NETWORK } from '../config.js'
9
15
  import type { DaoEvent } from '../composables/useDaoEvents.js'
10
16
 
@@ -27,20 +33,23 @@ const eventLabel: Record<DaoEvent['type'], string> = {
27
33
  AccessBurned: 'Access Burned',
28
34
  }
29
35
 
36
+ function badgeVariant(type: DaoEvent['type']): 'active' | 'closed' {
37
+ return type === 'AccessMinted' ? 'active' : 'closed'
38
+ }
30
39
  </script>
31
40
 
32
41
  <template>
33
42
  <div>
34
43
  <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px">
35
44
  <p class="dao-section-title" style="margin: 0">On-Chain Events</p>
36
- <button class="dao-toolbar__btn" :disabled="loading" @click="reload">
45
+ <UiToolbarButton :disabled="loading" @click="reload">
37
46
  {{ loading ? 'Loading…' : 'Refresh' }}
38
- </button>
47
+ </UiToolbarButton>
39
48
  </div>
40
49
 
41
50
  <p v-if="error" class="dao-muted">{{ error }}</p>
42
51
 
43
- <DataTable v-if="pageEvents.length || loading" :empty="'No events'">
52
+ <UiDataTable v-if="pageEvents.length || loading" :empty="'No events'">
44
53
  <template #head>
45
54
  <th>Type</th>
46
55
  <th>
@@ -57,15 +66,7 @@ const eventLabel: Record<DaoEvent['type'], string> = {
57
66
  </th>
58
67
  </template>
59
68
  <tr v-for="ev in pageEvents" :key="ev.txDigest">
60
- <td>
61
- <span
62
- class="dao-badge"
63
- :class="{
64
- 'dao-badge--active': ev.type === 'AccessMinted',
65
- 'dao-badge--closed': ev.type === 'AccessConsumed' || ev.type === 'AccessBurned',
66
- }"
67
- >{{ eventLabel[ev.type] }}</span>
68
- </td>
69
+ <td><UiBadge :variant="badgeVariant(ev.type)">{{ eventLabel[ev.type] }}</UiBadge></td>
69
70
  <td class="dao-mono" style="font-size: 0.72rem; white-space: nowrap">
70
71
  <CopyableAddress v-if="ev.address && ev.address !== 'undefined'" :address="ev.address">
71
72
  <ExplorerLink :href="suiExplorerUrl('account', ev.address, NETWORK)" :value="ev.address" />
@@ -81,13 +82,13 @@ const eventLabel: Record<DaoEvent['type'], string> = {
81
82
  </CopyableAddress>
82
83
  </td>
83
84
  </tr>
84
- </DataTable>
85
+ </UiDataTable>
85
86
  <p v-else-if="!loading" class="dao-placeholder">No events found.</p>
86
87
 
87
88
  <div v-if="totalPages > 1" class="dao-pagination">
88
- <button class="dao-toolbar__btn" :disabled="page === 1" @click="prevPage">← Prev</button>
89
+ <UiToolbarButton :disabled="page === 1" @click="prevPage">← Prev</UiToolbarButton>
89
90
  <span class="dao-muted" style="font-size: 0.78rem">Page {{ page }} / {{ totalPages }}</span>
90
- <button class="dao-toolbar__btn" :disabled="page === totalPages" @click="nextPage">Next →</button>
91
+ <UiToolbarButton :disabled="page === totalPages" @click="nextPage">Next →</UiToolbarButton>
91
92
  </div>
92
93
  </div>
93
94
  </template>
@@ -2,13 +2,21 @@
2
2
  // Overview tab: treasury balance + commission, platform activity counters (active gates,
3
3
  // accesses minted/consumed derived from the event feed), and a recent-activity list.
4
4
  import { computed } from 'vue'
5
- import Panel from '../components/Panel.vue'
6
5
  import AmountCell from '../components/AmountCell.vue'
7
6
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
8
7
  import { useTreasury } from '../composables/useTreasury.js'
9
8
  import { useDaoEvents } from '../composables/useDaoEvents.js'
10
9
  import { useGates } from '../composables/useGates.js'
11
- import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
10
+ import {
11
+ CopyableAddress,
12
+ ExplorerLink,
13
+ suiExplorerUrl,
14
+ UiPanel,
15
+ UiStatGrid,
16
+ UiStatRow,
17
+ UiActivityFeed,
18
+ UiActivityItem,
19
+ } from '@meddleware/ui'
12
20
  import { NETWORK } from '../config.js'
13
21
  import type { DaoEvent } from '../composables/useDaoEvents.js'
14
22
 
@@ -35,49 +43,40 @@ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'A
35
43
  <div class="dao-overview-cols">
36
44
  <!-- Left: treasury summary -->
37
45
  <div style="display: flex; flex-direction: column; gap: 8px">
38
- <Panel title="Treasury Balance">
39
- <div class="dao-stat-grid">
40
- <span class="dao-stat-grid__label">SUI balance</span>
41
- <AmountCell class="dao-stat-grid__value" :mist="balance" />
42
-
43
- <span class="dao-stat-grid__label">Commission rate</span>
44
- <span class="dao-stat-grid__value dao-mono">{{ commissionPct }}</span>
45
-
46
- <span class="dao-stat-grid__label">Treasury address</span>
47
- <span class="dao-stat-grid__value" style="text-align: left">
46
+ <UiPanel title="Treasury Balance">
47
+ <UiStatGrid>
48
+ <UiStatRow label="SUI balance"><AmountCell :mist="balance" /></UiStatRow>
49
+ <UiStatRow label="Commission rate">{{ commissionPct }}</UiStatRow>
50
+ <UiStatRow label="Treasury address" align="left">
48
51
  <CopyableAddress v-if="config?.treasury" :address="config.treasury">
49
52
  <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
50
53
  </CopyableAddress>
51
54
  <span v-else class="dao-mono" style="font-size: 0.7rem">—</span>
52
- </span>
53
- </div>
54
- </Panel>
55
-
56
- <Panel title="Platform Activity">
57
- <div class="dao-stat-grid">
58
- <span class="dao-stat-grid__label">Active gates</span>
59
- <span class="dao-stat-grid__value dao-mono">{{ gatesLoading ? '…' : gates.length }}</span>
60
-
61
- <span class="dao-stat-grid__label">Accesses minted</span>
62
- <span class="dao-stat-grid__value dao-mono">{{ accessesMinted || '—' }}</span>
55
+ </UiStatRow>
56
+ </UiStatGrid>
57
+ </UiPanel>
63
58
 
64
- <span class="dao-stat-grid__label">Accesses consumed</span>
65
- <span class="dao-stat-grid__value dao-mono">{{ accessesConsumed || '—' }}</span>
66
- </div>
67
- </Panel>
59
+ <UiPanel title="Platform Activity">
60
+ <UiStatGrid>
61
+ <UiStatRow label="Active gates">{{ gatesLoading ? '…' : gates.length }}</UiStatRow>
62
+ <UiStatRow label="Accesses minted">{{ accessesMinted || '—' }}</UiStatRow>
63
+ <UiStatRow label="Accesses consumed">{{ accessesConsumed || '—' }}</UiStatRow>
64
+ </UiStatGrid>
65
+ </UiPanel>
68
66
  </div>
69
67
 
70
68
  <!-- Right: recent activity -->
71
- <Panel title="Recent Activity">
69
+ <UiPanel title="Recent Activity">
72
70
  <p v-if="eventsLoading" class="dao-muted" style="margin: 0">Loading events…</p>
73
- <ul v-else-if="events.length" class="dao-feed">
74
- <li v-for="ev in events" :key="ev.txDigest" class="dao-feed__item">
75
- <span class="dao-feed__dot" />
76
- <span class="dao-feed__type">{{ eventLabel[ev.type] }}</span>
77
- <span class="dao-feed__time">ckpt {{ ev.checkpoint ?? '?' }}</span>
78
- </li>
79
- </ul>
71
+ <UiActivityFeed v-else-if="events.length">
72
+ <UiActivityItem
73
+ v-for="ev in events"
74
+ :key="ev.txDigest"
75
+ :type="eventLabel[ev.type]"
76
+ :time="`ckpt ${ev.checkpoint ?? '?'}`"
77
+ />
78
+ </UiActivityFeed>
80
79
  <p v-else class="dao-placeholder">No recent events.</p>
81
- </Panel>
80
+ </UiPanel>
82
81
  </div>
83
82
  </template>
@@ -2,13 +2,19 @@
2
2
  // Treasury tab: commission rate + live SUI balance + treasury address, plus the table
3
3
  // of Community Gates the treasury controls (discovered via AdminCap ownership).
4
4
  import { computed } from 'vue'
5
- import Panel from '../components/Panel.vue'
6
5
  import AmountCell from '../components/AmountCell.vue'
7
- import DataTable from '../components/DataTable.vue'
8
6
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
9
7
  import { useTreasury } from '../composables/useTreasury.js'
10
8
  import { useGates } from '../composables/useGates.js'
11
- import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
9
+ import {
10
+ CopyableAddress,
11
+ ExplorerLink,
12
+ suiExplorerUrl,
13
+ UiPanel,
14
+ UiStatGrid,
15
+ UiStatRow,
16
+ UiDataTable,
17
+ } from '@meddleware/ui'
12
18
  import { NETWORK } from '../config.js'
13
19
 
14
20
  const { config, loading: cfgLoading, error: cfgErr } = usePlatformConfig()
@@ -27,31 +33,26 @@ function formatPrice(mist: bigint): string {
27
33
 
28
34
  <template>
29
35
  <div style="display: flex; flex-direction: column; gap: 10px">
30
- <Panel title="Treasury">
36
+ <UiPanel title="Treasury">
31
37
  <p v-if="cfgLoading" class="dao-muted">Loading…</p>
32
38
  <p v-else-if="cfgErr" class="dao-muted">{{ cfgErr }}</p>
33
39
  <template v-else-if="config">
34
- <div class="dao-stat-grid" style="max-width: 480px">
35
- <span class="dao-stat-grid__label">Commission rate</span>
36
- <span class="dao-stat-grid__value dao-mono">{{ commissionPct }} ({{ config.commissionBps }} bps)</span>
37
-
38
- <span class="dao-stat-grid__label">Current balance</span>
39
- <AmountCell class="dao-stat-grid__value" :mist="balance" />
40
-
41
- <span class="dao-stat-grid__label">Treasury address</span>
42
- <span class="dao-stat-grid__value" style="text-align: left">
40
+ <UiStatGrid style="max-width: 480px">
41
+ <UiStatRow label="Commission rate">{{ commissionPct }} ({{ config.commissionBps }} bps)</UiStatRow>
42
+ <UiStatRow label="Current balance"><AmountCell :mist="balance" /></UiStatRow>
43
+ <UiStatRow label="Treasury address" align="left">
43
44
  <CopyableAddress :address="config.treasury">
44
45
  <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
45
46
  </CopyableAddress>
46
- </span>
47
- </div>
47
+ </UiStatRow>
48
+ </UiStatGrid>
48
49
  </template>
49
50
  <p v-else class="dao-muted">PlatformConfig not loaded.</p>
50
- </Panel>
51
+ </UiPanel>
51
52
 
52
- <Panel title="Community Gates">
53
+ <UiPanel title="Community Gates">
53
54
  <p v-if="gatesLoading" class="dao-muted">Loading gates…</p>
54
- <DataTable v-else-if="gates.length" :empty="'No gates found'">
55
+ <UiDataTable v-else-if="gates.length" :empty="'No gates found'">
55
56
  <template #head>
56
57
  <th>Gate name</th>
57
58
  <th>Price</th>
@@ -66,8 +67,8 @@ function formatPrice(mist: bigint): string {
66
67
  </CopyableAddress>
67
68
  </td>
68
69
  </tr>
69
- </DataTable>
70
+ </UiDataTable>
70
71
  <p v-else class="dao-placeholder">No gates found.</p>
71
- </Panel>
72
+ </UiPanel>
72
73
  </div>
73
74
  </template>
@@ -1,26 +0,0 @@
1
- <script setup lang="ts">
2
- // Scrollable table shell with a `head` slot (header cells) and default slot (rows).
3
- // Renders the `empty` message when no rows are provided.
4
- defineProps<{ empty?: string }>()
5
-
6
- const slots = defineSlots<{
7
- head(): unknown
8
- default(): unknown
9
- }>()
10
- </script>
11
-
12
- <template>
13
- <div style="overflow-x: auto">
14
- <table class="dao-table">
15
- <thead>
16
- <tr>
17
- <slot name="head" />
18
- </tr>
19
- </thead>
20
- <tbody>
21
- <slot />
22
- </tbody>
23
- </table>
24
- <p v-if="empty && !slots.default" class="dao-placeholder">{{ empty }}</p>
25
- </div>
26
- </template>
@@ -1,13 +0,0 @@
1
- <script setup lang="ts">
2
- // Titled content panel — the console's basic surface primitive. Optional heading + default slot.
3
- defineProps<{ title?: string }>()
4
- </script>
5
-
6
- <template>
7
- <div class="dao-panel">
8
- <div v-if="title" class="dao-panel__head">{{ title }}</div>
9
- <div class="dao-panel__body">
10
- <slot />
11
- </div>
12
- </div>
13
- </template>