@meddleware/dao-ui 0.1.19 → 0.1.20

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.19",
3
+ "version": "0.1.20",
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>",
@@ -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.31.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.0",
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/
22
22
  </template>
23
23
  </AppHeader>
24
24
 
25
- <DaoView style="flex: 1; min-height: 0" />
25
+ <DaoView />
26
26
 
27
27
  <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL">
28
28
  <template #start>
package/src/DaoView.vue CHANGED
@@ -7,9 +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'
11
- import TabBar from './components/TabBar.vue'
12
- import StatusBar from './components/StatusBar.vue'
10
+ import { AppTabNav, UiStatusBar, UiTabPanel, UiToolbar, type AppTab } from '@meddleware/ui'
13
11
  import OverviewTab from './tabs/OverviewTab.vue'
14
12
  import TreasuryTab from './tabs/TreasuryTab.vue'
15
13
  import ProposalsTab from './tabs/ProposalsTab.vue'
@@ -17,10 +15,9 @@ import GovernanceTab from './tabs/GovernanceTab.vue'
17
15
  import HistoryTab from './tabs/HistoryTab.vue'
18
16
  import { useDaoEvents } from './composables/useDaoEvents.js'
19
17
  import { useEpoch } from './composables/useEpoch.js'
18
+ import { NETWORK } from './config.js'
20
19
 
21
- import type { Tab } from './components/TabBar.vue'
22
-
23
- const TABS: Tab[] = [
20
+ const TABS: AppTab[] = [
24
21
  { id: 'overview', label: 'Overview' },
25
22
  { id: 'treasury', label: 'Treasury' },
26
23
  { id: 'proposals', label: 'Proposals' },
@@ -46,20 +43,14 @@ const { epoch } = useEpoch()
46
43
 
47
44
  <template>
48
45
  <div class="dao-view">
49
- <UiToolbar>
50
- <UiToolbarButton @click="activeTab = 'overview'">🏛 Meddleware DAO</UiToolbarButton>
51
- </UiToolbar>
46
+ <UiToolbar :actions="[{ id: 'home', label: '🏛 Meddleware DAO' }]" @action="activeTab = 'overview'" />
52
47
 
53
- <TabBar :tabs="TABS" v-model="activeTab" />
48
+ <AppTabNav v-model="activeTab" :tabs="TABS" id-prefix="dao" variant="raised" aria-label="DAO sections" />
54
49
 
55
- <div class="dao-content">
50
+ <UiTabPanel id-prefix="dao" :tab="activeTab" class="dao-content">
56
51
  <component :is="activeComponent" />
57
- </div>
52
+ </UiTabPanel>
58
53
 
59
- <StatusBar
60
- :epoch="epoch"
61
- :last-refresh="lastRefresh"
62
- :error="!!eventsError"
63
- />
54
+ <UiStatusBar :network="NETWORK" :healthy="!eventsError" :epoch="epoch" :last-refresh="lastRefresh" />
64
55
  </div>
65
56
  </template>
@@ -33,36 +33,65 @@ function formatSui(mist: bigint): string {
33
33
  </script>
34
34
 
35
35
  <template>
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>
41
- </div>
42
- <span class="dao-muted" style="white-space: nowrap; font-family: var(--mw-font-mono); font-size: 0.75rem">
36
+ <UiPanel class="proposal">
37
+ <header class="proposal__head">
38
+ <UiBadge :variant="badgeVariant">{{ proposal.status }}</UiBadge>
39
+ <h3 class="proposal__title">{{ proposal.title }}</h3>
40
+ <small class="proposal__epochs">
43
41
  <template v-if="epochsLeft !== null && epochsLeft > 0">{{ epochsLeft }} epochs left</template>
44
42
  <template v-else-if="epochsLeft !== null && epochsLeft <= 0">Expired</template>
45
- </span>
46
- </div>
43
+ </small>
44
+ </header>
47
45
 
48
- <p class="dao-muted" style="margin: 0 0 8px; font-size: 0.78rem">{{ proposal.description }}</p>
46
+ <p class="dao-muted proposal__desc">{{ proposal.description }}</p>
49
47
 
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>
57
- </div>
58
- </div>
48
+ <progress class="dao-progress" :value="pct" max="100" aria-label="Funding progress">{{ pct }}%</progress>
49
+ <p class="proposal__totals">
50
+ <span>{{ formatSui(proposal.contributedMist) }} SUI raised</span>
51
+ <span>{{ pct }}% of {{ formatSui(proposal.targetMist) }} SUI target</span>
52
+ </p>
59
53
 
60
- <UiToolbarButton
61
- disabled
62
- title="Vault DAO governance module launching soon"
63
- style="margin-top: 6px; opacity: 0.5; cursor: not-allowed"
64
- >
54
+ <UiToolbarButton disabled title="Vault DAO governance module launching soon" class="proposal__contribute">
65
55
  Contribute
66
56
  </UiToolbarButton>
67
57
  </UiPanel>
68
58
  </template>
59
+
60
+ <style scoped>
61
+ .proposal {
62
+ margin-bottom: 8px;
63
+ }
64
+ .proposal__head {
65
+ display: flex;
66
+ align-items: flex-start;
67
+ gap: 6px;
68
+ margin-bottom: 6px;
69
+ }
70
+ .proposal__title {
71
+ margin: 0;
72
+ font-size: 0.85rem;
73
+ font-weight: 700;
74
+ }
75
+ .proposal__epochs {
76
+ margin-inline-start: auto;
77
+ padding-inline-start: 8px;
78
+ white-space: nowrap;
79
+ font-family: var(--mw-font-mono);
80
+ font-size: 0.75rem;
81
+ color: var(--muted);
82
+ }
83
+ .proposal__desc {
84
+ margin: 0 0 8px;
85
+ }
86
+ .proposal__totals {
87
+ display: flex;
88
+ justify-content: space-between;
89
+ margin: 2px 0 4px;
90
+ font-size: 0.72rem;
91
+ color: var(--muted);
92
+ font-family: var(--mw-font-mono);
93
+ }
94
+ .proposal__contribute {
95
+ margin-top: 6px;
96
+ }
97
+ </style>
@@ -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
@@ -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;
@@ -50,20 +50,20 @@ watch(
50
50
  </script>
51
51
 
52
52
  <template>
53
- <div style="display: flex; flex-direction: column; gap: 10px">
53
+ <div class="dao-stack gov">
54
54
  <UiPanel title="Platform Parameters">
55
- <UiStatGrid style="max-width: 480px">
55
+ <UiStatGrid class="dao-stat-grid">
56
56
  <UiStatRow label="Commission rate">{{ commissionPct }}</UiStatRow>
57
57
  <UiStatRow label="Max commission cap">10.00% (1000 bps — on-chain hard cap)</UiStatRow>
58
58
  <UiStatRow label="Treasury address" align="left">
59
59
  <CopyableAddress v-if="config?.treasury" :address="config.treasury">
60
60
  <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
61
61
  </CopyableAddress>
62
- <span v-else class="dao-mono" style="font-size: 0.7rem">—</span>
62
+ <span v-else class="dao-mono dao-mono--sm">—</span>
63
63
  </UiStatRow>
64
64
  </UiStatGrid>
65
65
 
66
- <p class="dao-muted" style="margin: 10px 0 0; font-size: 0.72rem">
66
+ <p class="dao-muted gov__footnote">
67
67
  Parameters are governed by <code>PlatformAdminCap</code> on-chain.
68
68
  Commission is charged on every NFT access purchase:
69
69
  <code>commission_bps / 10000 × price</code> routes to treasury;
@@ -74,31 +74,31 @@ watch(
74
74
  <UiPanel title="Admin Actions">
75
75
  <!-- Not connected -->
76
76
  <template v-if="!account">
77
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
77
+ <p class="dao-muted">
78
78
  Connect a wallet via the header to manage platform settings.
79
79
  </p>
80
80
  </template>
81
81
 
82
82
  <!-- Connected, checking -->
83
83
  <template v-else-if="capState === 'checking'">
84
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
84
+ <p class="dao-muted">
85
85
  Checking capabilities for {{ account.address.slice(0, 10) }}…
86
86
  </p>
87
87
  </template>
88
88
 
89
89
  <!-- Connected, has PlatformAdminCap -->
90
90
  <template v-else-if="capState === 'found'">
91
- <p class="dao-muted" style="margin: 0 0 8px; font-size: 0.78rem">
92
- <span style="color: var(--ok)">●</span>
91
+ <p class="dao-muted">
92
+ <span class="gov__ok" aria-hidden="true">●</span>
93
93
  <code>PlatformAdminCap</code> detected. Admin controls will appear here when implemented.
94
94
  </p>
95
95
  </template>
96
96
 
97
97
  <!-- Connected, no cap -->
98
98
  <template v-else-if="capState === 'not-found'">
99
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
100
- <span>●</span>
101
- Connected: {{ account.address.slice(0, 10) }}…{{ account.address.slice(-6) }}<br>
99
+ <p class="dao-muted">
100
+ <span aria-hidden="true">●</span>
101
+ Connected: {{ account.address.slice(0, 10) }}…{{ account.address.slice(-6) }}<br />
102
102
  This wallet does not hold <code>PlatformAdminCap</code> or <code>DaoAdminCap</code>.
103
103
  Privileged actions are not available.
104
104
  </p>
@@ -106,10 +106,23 @@ watch(
106
106
 
107
107
  <!-- Error -->
108
108
  <template v-else-if="capState === 'error'">
109
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
109
+ <p class="dao-muted">
110
110
  Unable to verify capabilities. Check your connection and try again.
111
111
  </p>
112
112
  </template>
113
113
  </UiPanel>
114
114
  </div>
115
115
  </template>
116
+
117
+ <style scoped>
118
+ .gov p {
119
+ margin: 0;
120
+ }
121
+ .gov .gov__footnote {
122
+ margin-top: 10px;
123
+ font-size: 0.72rem;
124
+ }
125
+ .gov__ok {
126
+ color: var(--ok);
127
+ }
128
+ </style>
@@ -39,56 +39,72 @@ function badgeVariant(type: DaoEvent['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">On-Chain Events</p>
45
- <UiToolbarButton :disabled="loading" @click="reload">
46
- {{ loading ? 'Loading…' : 'Refresh' }}
47
- </UiToolbarButton>
48
- </div>
42
+ <header class="history__head">
43
+ <h2 class="dao-section-title">On-Chain 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 history__cell history__cell--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 history__cell">
76
+ {{ ev.checkpoint ? '#' + ev.checkpoint : '—' }}
77
+ </td>
78
+ <td class="dao-mono history__cell history__cell--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
+ .history__head {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ margin-bottom: 8px;
100
+ }
101
+ .history__head .dao-section-title {
102
+ margin: 0;
103
+ }
104
+ .history__cell {
105
+ font-size: 0.72rem;
106
+ }
107
+ .history__cell--nowrap {
108
+ white-space: nowrap;
109
+ }
110
+ </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"
@@ -10,18 +10,13 @@ const { epoch } = useEpoch()
10
10
  </script>
11
11
 
12
12
  <template>
13
- <div>
14
- <template v-if="proposals.length > 0">
15
- <p class="dao-section-title">Active &amp; Pending Proposals</p>
16
- <ProposalRow
17
- v-for="p in proposals"
18
- :key="p.id"
19
- :proposal="p"
20
- :current-epoch="epoch"
21
- />
22
- </template>
23
- <div v-else class="dao-placeholder">
24
- No proposals yet.
25
- </div>
26
- </div>
13
+ <template v-if="proposals.length > 0">
14
+ <h2 class="dao-section-title">Active &amp; Pending Proposals</h2>
15
+ <ul role="list">
16
+ <li v-for="p in proposals" :key="p.id">
17
+ <ProposalRow :proposal="p" :current-epoch="epoch" />
18
+ </li>
19
+ </ul>
20
+ </template>
21
+ <p v-else class="dao-placeholder">No proposals yet.</p>
27
22
  </template>
@@ -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="Treasury">
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>
@@ -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 dao-ui'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="DAO sections"
17
- @update:model-value="emit('update:modelValue', $event)"
18
- />
19
- </template>