@africode/core 5.0.8 → 5.0.9
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/AGENT_INSTRUCTIONS.md +595 -595
- package/COMPONENT_SCHEMA.json +1800 -991
- package/bin/create-africode.js +87 -25
- package/components/auth-form.js +154 -0
- package/components/index.js +10 -0
- package/components/kyc-upload.js +173 -0
- package/components/nav-drawer.js +217 -0
- package/components/transaction-ledger.js +138 -0
- package/components/wallet-balance.js +114 -0
- package/core/a2ui-schema-manager.js +1 -1
- package/core/a2ui.js +178 -1
- package/core/bun-runtime.js +122 -7
- package/core/cli/commands/build.js +30 -5
- package/core/cli/ui.js +13 -3
- package/core/compliance.js +201 -0
- package/core/middleware.js +80 -17
- package/core/patterns.js +168 -0
- package/core/request-analytics.js +254 -0
- package/core/request-identity.js +79 -29
- package/core/sdk.js +4 -1
- package/core/validation.js +13 -0
- package/dist/africode.js +858 -457
- package/dist/africode.js.map +14 -9
- package/dist/build-info.json +3 -3
- package/dist/components.js +784 -383
- package/dist/components.js.map +11 -6
- package/package.json +1 -1
- package/templates/starter/package.json +5 -5
- package/templates/starter/src/index.js +18 -0
- package/templates/starter/src/pages/index.js +1 -1
- package/templates/starter-3d/package.json +5 -5
- package/templates/starter-3d/src/pages/index.js +1 -1
- package/templates/starter-dashboard/.env.example +21 -0
- package/templates/starter-dashboard/africode.config.js +20 -0
- package/templates/starter-dashboard/package.json +14 -0
- package/templates/starter-dashboard/src/index.js +17 -0
- package/templates/starter-dashboard/src/pages/api/analytics.js +24 -0
- package/templates/starter-dashboard/src/pages/index.html +118 -0
- package/templates/starter-dashboard/src/pages/index.js +110 -0
- package/templates/starter-dashboard/src/styles/main.css +172 -0
- package/templates/starter-fintech/.env.example +28 -0
- package/templates/starter-fintech/africode.config.js +20 -0
- package/templates/starter-fintech/package.json +15 -0
- package/templates/starter-fintech/src/index.js +17 -0
- package/templates/starter-fintech/src/pages/api/auth.js +45 -0
- package/templates/starter-fintech/src/pages/api/transfer.js +65 -0
- package/templates/starter-fintech/src/pages/api/wallet.js +39 -0
- package/templates/starter-fintech/src/pages/api/webhooks/payment.js +32 -0
- package/templates/starter-fintech/src/pages/index.html +169 -0
- package/templates/starter-fintech/src/pages/index.js +161 -0
- package/templates/starter-fintech/src/styles/main.css +246 -0
- package/templates/starter-react/package.json +5 -5
- package/templates/starter-react/src/pages/index.js +1 -1
- package/templates/starter-tailwind/package.json +5 -5
- package/templates/starter-tailwind/src/pages/index.js +1 -1
- package/templates/starter-website/.env.example +18 -0
- package/templates/starter-website/africode.config.js +20 -0
- package/templates/starter-website/package.json +14 -0
- package/templates/starter-website/src/index.js +17 -0
- package/templates/starter-website/src/pages/index.html +124 -0
- package/templates/starter-website/src/pages/index.js +116 -0
- package/templates/starter-website/src/styles/main.css +195 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Navigation Drawer Component
|
|
3
|
+
*
|
|
4
|
+
* A responsive navigation drawer that works on mobile and desktop
|
|
5
|
+
* with smooth animations and accessibility features.
|
|
6
|
+
*
|
|
7
|
+
* @module components/nav-drawer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AfriCodeComponent, html } from './base.js';
|
|
11
|
+
|
|
12
|
+
export class AfriNavDrawer extends AfriCodeComponent {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.isOpen = false;
|
|
16
|
+
this.position = this.getAttribute('position') || 'left'; // left or right
|
|
17
|
+
this.width = this.getAttribute('width') || '280px';
|
|
18
|
+
this.overlay = this.hasAttribute('overlay');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static get observedAttributes() {
|
|
22
|
+
return ['open', 'position', 'width', 'overlay'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
26
|
+
switch (name) {
|
|
27
|
+
case 'open':
|
|
28
|
+
this.isOpen = newValue !== null;
|
|
29
|
+
this.render();
|
|
30
|
+
break;
|
|
31
|
+
case 'position':
|
|
32
|
+
this.position = newValue;
|
|
33
|
+
break;
|
|
34
|
+
case 'width':
|
|
35
|
+
this.width = newValue;
|
|
36
|
+
break;
|
|
37
|
+
case 'overlay':
|
|
38
|
+
this.overlay = newValue !== null;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
super.connectedCallback();
|
|
45
|
+
this.addEventListener('keydown', this.handleKeyDown);
|
|
46
|
+
this.loadStyles();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
disconnectedCallback() {
|
|
50
|
+
this.removeEventListener('keydown', this.handleKeyDown);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
handleKeyDown = (e) => {
|
|
54
|
+
if (e.key === 'Escape' && this.isOpen) {
|
|
55
|
+
this.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toggle() {
|
|
60
|
+
this.isOpen ? this.close() : this.open();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
open() {
|
|
64
|
+
this.isOpen = true;
|
|
65
|
+
this.setAttribute('open', '');
|
|
66
|
+
this.render();
|
|
67
|
+
this.emit('af-navdrawer-open', { drawer: this });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
close() {
|
|
71
|
+
this.isOpen = false;
|
|
72
|
+
this.removeAttribute('open');
|
|
73
|
+
this.render();
|
|
74
|
+
this.emit('af-navdrawer-close', { drawer: this });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
if (!this.shadowRoot) return;
|
|
79
|
+
|
|
80
|
+
// Apply styles first
|
|
81
|
+
this.shadowRoot.innerHTML = `
|
|
82
|
+
<style>
|
|
83
|
+
:host {
|
|
84
|
+
display: block;
|
|
85
|
+
position: fixed;
|
|
86
|
+
z-index: 1000;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.nav-drawer {
|
|
90
|
+
position: fixed;
|
|
91
|
+
top: 0;
|
|
92
|
+
${this.position}: 0;
|
|
93
|
+
width: ${this.width};
|
|
94
|
+
height: 100vh;
|
|
95
|
+
background: var(--af-bg, #ffffff);
|
|
96
|
+
box-shadow: var(--af-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
|
|
97
|
+
transform: translateX(${this.position === 'left' ? '-100%' : '100%'});
|
|
98
|
+
transition: transform 0.3s ease-in-out;
|
|
99
|
+
overflow-y: auto;
|
|
100
|
+
z-index: 1001;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.nav-drawer.open {
|
|
104
|
+
transform: translateX(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.nav-drawer-overlay {
|
|
108
|
+
position: fixed;
|
|
109
|
+
top: 0;
|
|
110
|
+
left: 0;
|
|
111
|
+
width: 100vw;
|
|
112
|
+
height: 100vh;
|
|
113
|
+
background: rgba(0, 0, 0, 0.5);
|
|
114
|
+
opacity: 0;
|
|
115
|
+
visibility: hidden;
|
|
116
|
+
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
|
117
|
+
z-index: 1000;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.nav-drawer-overlay.open {
|
|
121
|
+
opacity: 1;
|
|
122
|
+
visibility: visible;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.nav-drawer-header {
|
|
126
|
+
display: flex;
|
|
127
|
+
justify-content: space-between;
|
|
128
|
+
align-items: center;
|
|
129
|
+
padding: 1rem;
|
|
130
|
+
border-bottom: 1px solid var(--af-border-color, #e5e7eb);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.nav-drawer-title {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-size: 1.25rem;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.close-btn {
|
|
140
|
+
background: none;
|
|
141
|
+
border: none;
|
|
142
|
+
font-size: 1.5rem;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
padding: 0.25rem;
|
|
145
|
+
border-radius: 4px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.close-btn:hover {
|
|
149
|
+
background: var(--af-hover-bg, #f3f4f6);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.nav-drawer-content {
|
|
153
|
+
padding: 1rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.nav-item {
|
|
157
|
+
display: block;
|
|
158
|
+
padding: 0.75rem 1rem;
|
|
159
|
+
text-decoration: none;
|
|
160
|
+
color: var(--af-text, #1f2937);
|
|
161
|
+
border-radius: 6px;
|
|
162
|
+
margin-bottom: 0.25rem;
|
|
163
|
+
transition: background-color 0.2s;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.nav-item:hover {
|
|
167
|
+
background-color: var(--af-hover-bg, #f3f4f6);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.nav-item.active {
|
|
171
|
+
background-color: var(--af-primary, #10b981);
|
|
172
|
+
color: white;
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
175
|
+
|
|
176
|
+
${this.overlay ? `<div class="nav-drawer-overlay ${this.isOpen ? 'open' : ''}" part="overlay"></div>` : ''}
|
|
177
|
+
|
|
178
|
+
<aside class="nav-drawer ${this.isOpen ? 'open' : ''}" part="drawer" role="dialog" aria-modal="true" aria-label="Navigation menu">
|
|
179
|
+
<header class="nav-drawer-header" part="header">
|
|
180
|
+
<h2 class="nav-drawer-title" part="title"><slot name="title">Menu</slot></h2>
|
|
181
|
+
<button class="close-btn" part="close-button" @click="${() => this.close()}" aria-label="Close menu">✕</button>
|
|
182
|
+
</header>
|
|
183
|
+
<div class="nav-drawer-content" part="content">
|
|
184
|
+
<slot name="content"></slot>
|
|
185
|
+
</div>
|
|
186
|
+
</aside>
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
// Add event listener to overlay if it exists
|
|
190
|
+
if (this.overlay) {
|
|
191
|
+
const overlay = this.shadowRoot.querySelector('.nav-drawer-overlay');
|
|
192
|
+
if (overlay) {
|
|
193
|
+
overlay.addEventListener('click', () => this.close());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Allow programmatic access to menu items
|
|
199
|
+
getItems() {
|
|
200
|
+
return this.querySelectorAll('[slot="content"] .nav-item');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
setActiveItem(selector) {
|
|
204
|
+
const items = this.getItems();
|
|
205
|
+
items.forEach(item => item.classList.remove('active'));
|
|
206
|
+
const activeItem = this.querySelector(selector);
|
|
207
|
+
if (activeItem) {
|
|
208
|
+
activeItem.classList.add('active');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Register the component
|
|
214
|
+
import { registerComponent } from './base.js';
|
|
215
|
+
registerComponent('af-nav-drawer', AfriNavDrawer);
|
|
216
|
+
|
|
217
|
+
export default AfriNavDrawer;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
2
|
+
|
|
3
|
+
const styles = `
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
color: var(--af-smart-text, var(--af-text, #172033));
|
|
7
|
+
font-family: var(--af-font-family, Inter, system-ui, sans-serif);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.ledger {
|
|
11
|
+
display: grid;
|
|
12
|
+
gap: 10px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.row {
|
|
16
|
+
display: grid;
|
|
17
|
+
grid-template-columns: 1fr auto;
|
|
18
|
+
gap: 12px;
|
|
19
|
+
padding: 12px;
|
|
20
|
+
border: 1px solid var(--af-smart-border, var(--af-border, #d8dee8));
|
|
21
|
+
border-radius: 8px;
|
|
22
|
+
background: var(--af-smart-surface, var(--af-surface, #ffffff));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.status,
|
|
26
|
+
.empty {
|
|
27
|
+
color: var(--af-smart-muted, var(--af-muted, #647084));
|
|
28
|
+
font-size: 0.84rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.amount {
|
|
32
|
+
font-weight: 760;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.error {
|
|
36
|
+
color: var(--af-smart-danger, var(--af-danger, #b42318));
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
function formatCurrency(value, currency) {
|
|
41
|
+
return `${currency || 'TZS'} ${Number(value || 0).toLocaleString('en-US')}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class AfriTransactionLedger extends AfriCodeComponent {
|
|
45
|
+
static get observedAttributes() {
|
|
46
|
+
return ['endpoint'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
connectedCallback() {
|
|
50
|
+
this.renderShell('Loading transactions');
|
|
51
|
+
this.load();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributeChangedCallback() {
|
|
55
|
+
if (this.isConnected) {
|
|
56
|
+
this.load();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get endpoint() {
|
|
61
|
+
return this.getAttribute('endpoint') || '/api/wallet';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
renderShell(message = '') {
|
|
65
|
+
this.shadowRoot.innerHTML = `
|
|
66
|
+
<style>${styles}</style>
|
|
67
|
+
<div class="ledger" role="list"></div>
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
if (message) {
|
|
71
|
+
const empty = document.createElement('div');
|
|
72
|
+
empty.className = 'empty';
|
|
73
|
+
empty.textContent = message;
|
|
74
|
+
this.shadowRoot.querySelector('.ledger').append(empty);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
renderTransactions(transactions) {
|
|
79
|
+
this.renderShell();
|
|
80
|
+
const ledger = this.shadowRoot.querySelector('.ledger');
|
|
81
|
+
|
|
82
|
+
if (!transactions.length) {
|
|
83
|
+
const empty = document.createElement('div');
|
|
84
|
+
empty.className = 'empty';
|
|
85
|
+
empty.textContent = 'No transactions yet.';
|
|
86
|
+
ledger.append(empty);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ledger.replaceChildren(...transactions.map((tx) => {
|
|
91
|
+
const row = document.createElement('article');
|
|
92
|
+
const details = document.createElement('div');
|
|
93
|
+
const counterparty = document.createElement('strong');
|
|
94
|
+
const status = document.createElement('div');
|
|
95
|
+
const amount = document.createElement('span');
|
|
96
|
+
|
|
97
|
+
row.className = 'row';
|
|
98
|
+
row.setAttribute('role', 'listitem');
|
|
99
|
+
status.className = 'status';
|
|
100
|
+
amount.className = 'amount';
|
|
101
|
+
counterparty.textContent = tx.counterparty || tx.id || 'Transaction';
|
|
102
|
+
status.textContent = tx.status || tx.type || 'pending';
|
|
103
|
+
amount.textContent = formatCurrency(tx.amount, tx.currency);
|
|
104
|
+
|
|
105
|
+
details.append(counterparty, status);
|
|
106
|
+
row.append(details, amount);
|
|
107
|
+
return row;
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
renderError(message) {
|
|
112
|
+
this.shadowRoot.innerHTML = `
|
|
113
|
+
<style>${styles}</style>
|
|
114
|
+
<div class="empty error"></div>
|
|
115
|
+
`;
|
|
116
|
+
this.shadowRoot.querySelector('.empty').textContent = message;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async load() {
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(this.endpoint);
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
const transactions = Array.isArray(data.transactions) ? data.transactions : [];
|
|
124
|
+
|
|
125
|
+
if (!response.ok || data.ok === false) {
|
|
126
|
+
throw new Error(data.error || 'Ledger data unavailable.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.renderTransactions(transactions);
|
|
130
|
+
this.emit('af-ledger-load', { transactions });
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.renderError('Transaction ledger unavailable.');
|
|
133
|
+
this.emit('af-ledger-error', { message: error.message });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
registerComponent('af-transaction-ledger', AfriTransactionLedger);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
2
|
+
|
|
3
|
+
const styles = `
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
color: var(--af-smart-text, var(--af-text, #172033));
|
|
7
|
+
font-family: var(--af-font-family, Inter, system-ui, sans-serif);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.wallet {
|
|
11
|
+
display: grid;
|
|
12
|
+
gap: 8px;
|
|
13
|
+
padding: 16px;
|
|
14
|
+
border: 1px solid var(--af-smart-border, var(--af-border, #d8dee8));
|
|
15
|
+
border-radius: 8px;
|
|
16
|
+
background: var(--af-smart-surface, var(--af-surface, #ffffff));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.label,
|
|
20
|
+
.meta {
|
|
21
|
+
color: var(--af-smart-muted, var(--af-muted, #647084));
|
|
22
|
+
font-size: 0.84rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.amount {
|
|
26
|
+
font-size: 1.8rem;
|
|
27
|
+
font-weight: 760;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.error {
|
|
31
|
+
color: var(--af-smart-danger, var(--af-danger, #b42318));
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
function formatCurrency(value, currency) {
|
|
36
|
+
return `${currency || 'TZS'} ${Number(value || 0).toLocaleString('en-US')}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class AfriWalletBalance extends AfriCodeComponent {
|
|
40
|
+
static get observedAttributes() {
|
|
41
|
+
return ['endpoint'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
connectedCallback() {
|
|
45
|
+
this.renderLoading();
|
|
46
|
+
this.load();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
attributeChangedCallback() {
|
|
50
|
+
if (this.isConnected) {
|
|
51
|
+
this.load();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get endpoint() {
|
|
56
|
+
return this.getAttribute('endpoint') || '/api/wallet';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
renderLoading() {
|
|
60
|
+
this.shadowRoot.innerHTML = `
|
|
61
|
+
<style>${styles}</style>
|
|
62
|
+
<article class="wallet">
|
|
63
|
+
<span class="label">Available balance</span>
|
|
64
|
+
<strong class="amount">Loading</strong>
|
|
65
|
+
<span class="meta">Fetching wallet</span>
|
|
66
|
+
</article>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
renderWallet(wallet) {
|
|
71
|
+
this.shadowRoot.innerHTML = `
|
|
72
|
+
<style>${styles}</style>
|
|
73
|
+
<article class="wallet">
|
|
74
|
+
<span class="label">Available balance</span>
|
|
75
|
+
<strong class="amount"></strong>
|
|
76
|
+
<span class="meta"></span>
|
|
77
|
+
</article>
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
this.shadowRoot.querySelector('.amount').textContent = formatCurrency(wallet.availableBalance, wallet.currency);
|
|
81
|
+
this.shadowRoot.querySelector('.meta').textContent = `Ledger ${formatCurrency(wallet.ledgerBalance, wallet.currency)} · Daily limit ${formatCurrency(wallet.dailyLimit, wallet.currency)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
renderError(message) {
|
|
85
|
+
this.shadowRoot.innerHTML = `
|
|
86
|
+
<style>${styles}</style>
|
|
87
|
+
<article class="wallet">
|
|
88
|
+
<span class="label">Available balance</span>
|
|
89
|
+
<strong class="amount error">Unavailable</strong>
|
|
90
|
+
<span class="meta error"></span>
|
|
91
|
+
</article>
|
|
92
|
+
`;
|
|
93
|
+
this.shadowRoot.querySelector('.meta').textContent = message;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async load() {
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(this.endpoint);
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
|
|
101
|
+
if (!response.ok || data.ok === false || !data.wallet) {
|
|
102
|
+
throw new Error(data.error || 'Wallet data unavailable.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.renderWallet(data.wallet);
|
|
106
|
+
this.emit('af-wallet-load', data);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
this.renderError('Wallet API is unavailable.');
|
|
109
|
+
this.emit('af-wallet-error', { message: error.message });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
registerComponent('af-wallet-balance', AfriWalletBalance);
|
|
@@ -146,7 +146,7 @@ export class A2UISchemaManager {
|
|
|
146
146
|
*/
|
|
147
147
|
extractComponentMetadata(filename, content) {
|
|
148
148
|
// Extract tagName
|
|
149
|
-
const tagMatch = content.match(/customElements\.define\(['"]([a-z0-9-]+)['"]/);
|
|
149
|
+
const tagMatch = content.match(/(?:customElements\.define|registerComponent)\(['"]([a-z0-9-]+)['"]/);
|
|
150
150
|
if (!tagMatch) return null;
|
|
151
151
|
|
|
152
152
|
const tagName = tagMatch[1];
|
package/core/a2ui.js
CHANGED
|
@@ -20,7 +20,13 @@ export const A2UI_TYPES = {
|
|
|
20
20
|
CARD: 'card',
|
|
21
21
|
MODAL: 'modal',
|
|
22
22
|
TOAST: 'toast',
|
|
23
|
-
PROGRESS: 'progress'
|
|
23
|
+
PROGRESS: 'progress',
|
|
24
|
+
CHART: 'chart',
|
|
25
|
+
DATATABLE: 'datatable',
|
|
26
|
+
TABS: 'tabs',
|
|
27
|
+
ACCORDION: 'accordion',
|
|
28
|
+
STEPPER: 'stepper',
|
|
29
|
+
DASHBOARD_WIDGET: 'dashboard-widget'
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
/**
|
|
@@ -167,6 +173,24 @@ export class A2UIRenderer {
|
|
|
167
173
|
case A2UI_TYPES.PROGRESS:
|
|
168
174
|
element = this.createProgress(props);
|
|
169
175
|
break;
|
|
176
|
+
case A2UI_TYPES.CHART:
|
|
177
|
+
element = this.createChart(props);
|
|
178
|
+
break;
|
|
179
|
+
case A2UI_TYPES.DATATABLE:
|
|
180
|
+
element = this.createDatatable(props);
|
|
181
|
+
break;
|
|
182
|
+
case A2UI_TYPES.TABS:
|
|
183
|
+
element = this.createTabs(props);
|
|
184
|
+
break;
|
|
185
|
+
case A2UI_TYPES.ACCORDION:
|
|
186
|
+
element = this.createAccordion(props);
|
|
187
|
+
break;
|
|
188
|
+
case A2UI_TYPES.STEPPER:
|
|
189
|
+
element = this.createStepper(props);
|
|
190
|
+
break;
|
|
191
|
+
case A2UI_TYPES.DASHBOARD_WIDGET:
|
|
192
|
+
element = this.createDashboardWidget(props);
|
|
193
|
+
break;
|
|
170
194
|
default:
|
|
171
195
|
throw new Error(`Unknown A2UI component type: ${type}`);
|
|
172
196
|
}
|
|
@@ -324,6 +348,159 @@ export class A2UIRenderer {
|
|
|
324
348
|
return progress;
|
|
325
349
|
}
|
|
326
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Create chart component
|
|
353
|
+
*/
|
|
354
|
+
createChart({ type = 'bar', data = [], labels = [], title, options = {} }) {
|
|
355
|
+
const chart = document.createElement('af-chart');
|
|
356
|
+
|
|
357
|
+
chart.setAttribute('type', type);
|
|
358
|
+
chart.setAttribute('data', JSON.stringify(data));
|
|
359
|
+
chart.setAttribute('labels', JSON.stringify(labels));
|
|
360
|
+
|
|
361
|
+
if (title) {
|
|
362
|
+
chart.setAttribute('title', title);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (options) {
|
|
366
|
+
chart.setAttribute('options', JSON.stringify(options));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return chart;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Create datatable component
|
|
374
|
+
*/
|
|
375
|
+
createDatatable({ columns = [], rows = [], title, options = {} }) {
|
|
376
|
+
const table = document.createElement('af-datatable');
|
|
377
|
+
|
|
378
|
+
table.setAttribute('columns', JSON.stringify(columns));
|
|
379
|
+
table.setAttribute('rows', JSON.stringify(rows));
|
|
380
|
+
|
|
381
|
+
if (title) {
|
|
382
|
+
table.setAttribute('title', title);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (options) {
|
|
386
|
+
table.setAttribute('options', JSON.stringify(options));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return table;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Create tabs component
|
|
394
|
+
*/
|
|
395
|
+
createTabs({ tabs = [], activeTab = 0 }) {
|
|
396
|
+
const tabsContainer = document.createElement('af-tabs');
|
|
397
|
+
tabsContainer.setAttribute('active', activeTab);
|
|
398
|
+
|
|
399
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
400
|
+
const tab = tabs[i];
|
|
401
|
+
const tabButton = document.createElement('af-tab-button');
|
|
402
|
+
tabButton.setAttribute('tab-id', i);
|
|
403
|
+
tabButton.textContent = tab.title;
|
|
404
|
+
|
|
405
|
+
const tabContent = document.createElement('af-tab-content');
|
|
406
|
+
tabContent.setAttribute('tab-id', i);
|
|
407
|
+
tabContent.innerHTML = tab.content || '';
|
|
408
|
+
|
|
409
|
+
tabsContainer.appendChild(tabButton);
|
|
410
|
+
tabsContainer.appendChild(tabContent);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return tabsContainer;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Create accordion component
|
|
418
|
+
*/
|
|
419
|
+
createAccordion({ items = [] }) {
|
|
420
|
+
const accordion = document.createElement('af-accordion');
|
|
421
|
+
|
|
422
|
+
for (const item of items) {
|
|
423
|
+
const panel = document.createElement('af-accordion-panel');
|
|
424
|
+
|
|
425
|
+
const header = document.createElement('af-accordion-header');
|
|
426
|
+
header.textContent = item.title;
|
|
427
|
+
|
|
428
|
+
const content = document.createElement('af-accordion-content');
|
|
429
|
+
content.innerHTML = item.content || '';
|
|
430
|
+
|
|
431
|
+
panel.appendChild(header);
|
|
432
|
+
panel.appendChild(content);
|
|
433
|
+
accordion.appendChild(panel);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return accordion;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Create stepper component
|
|
441
|
+
*/
|
|
442
|
+
createStepper({ steps = [], currentStep = 0 }) {
|
|
443
|
+
const stepper = document.createElement('af-stepper');
|
|
444
|
+
stepper.setAttribute('current-step', currentStep);
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < steps.length; i++) {
|
|
447
|
+
const step = document.createElement('af-step');
|
|
448
|
+
step.setAttribute('step-number', i + 1);
|
|
449
|
+
step.setAttribute('active', i <= currentStep);
|
|
450
|
+
|
|
451
|
+
if (i === currentStep) {
|
|
452
|
+
step.setAttribute('current', 'true');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
step.textContent = steps[i].title || `Step ${i + 1}`;
|
|
456
|
+
stepper.appendChild(step);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return stepper;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Create dashboard widget component
|
|
464
|
+
*/
|
|
465
|
+
createDashboardWidget({ title, content, type = 'card', size = 'medium', actions = [] }) {
|
|
466
|
+
const widget = document.createElement('af-dashboard-widget');
|
|
467
|
+
widget.setAttribute('type', type);
|
|
468
|
+
widget.setAttribute('size', size);
|
|
469
|
+
|
|
470
|
+
if (title) {
|
|
471
|
+
const header = document.createElement('h3');
|
|
472
|
+
header.className = 'widget-title';
|
|
473
|
+
header.textContent = title;
|
|
474
|
+
widget.appendChild(header);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (content) {
|
|
478
|
+
const contentEl = document.createElement('div');
|
|
479
|
+
contentEl.className = 'widget-content';
|
|
480
|
+
contentEl.innerHTML = typeof content === 'string' ? content : JSON.stringify(content);
|
|
481
|
+
widget.appendChild(contentEl);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (actions.length > 0) {
|
|
485
|
+
const actionsEl = document.createElement('div');
|
|
486
|
+
actionsEl.className = 'widget-actions';
|
|
487
|
+
|
|
488
|
+
for (const action of actions) {
|
|
489
|
+
const button = document.createElement('af-button');
|
|
490
|
+
button.textContent = action.label;
|
|
491
|
+
button.setAttribute('variant', action.variant || 'secondary');
|
|
492
|
+
if (action.onClick) {
|
|
493
|
+
button.addEventListener('click', action.onClick);
|
|
494
|
+
}
|
|
495
|
+
actionsEl.appendChild(button);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
widget.appendChild(actionsEl);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return widget;
|
|
502
|
+
}
|
|
503
|
+
|
|
327
504
|
/**
|
|
328
505
|
* Update existing component
|
|
329
506
|
*/
|