@africode/core 5.0.0

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.
Files changed (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * create-africode v5.0.0
4
+ *
5
+ * Scaffolds a new AfriCode project with autonomous lifecycle management
6
+ * usage: bunx create-africode my-app
7
+ *
8
+ * @module bin/scaffold
9
+ */
10
+
11
+ import { mkdir, writeFile, cp, readFile } from 'fs/promises';
12
+ import { existsSync } from 'fs';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const FRAMEWORK_ROOT = path.resolve(__dirname, '..');
19
+
20
+ const colors = {
21
+ green: '\x1b[32m',
22
+ gold: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ red: '\x1b[31m',
25
+ reset: '\x1b[0m',
26
+ bold: '\x1b[1m',
27
+ dim: '\x1b[2m'
28
+ };
29
+
30
+ export async function createProject(targetName?: string): Promise<void> {
31
+ const projectName = targetName || 'africode-app';
32
+
33
+ console.log(`
34
+ ${colors.gold}╔═══════════════════════════════════════╗
35
+ ║ ${colors.green}Creating AfriCode v5.0.0 Project${colors.gold} ║
36
+ ╚═══════════════════════════════════════╝${colors.reset}
37
+ `);
38
+
39
+ const projectDir = path.join(process.cwd(), projectName);
40
+
41
+ if (existsSync(projectDir)) {
42
+ console.log(`${colors.red}✗ Directory "${projectName}" already exists${colors.reset}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log(`${colors.dim} Creating ${projectName}...${colors.reset}\n`);
47
+
48
+ try {
49
+ // 1. Create Project Root
50
+ await mkdir(projectDir);
51
+
52
+ // 2. Create package.json
53
+ const packageJson = {
54
+ "name": projectName,
55
+ "version": "1.0.0",
56
+ "description": `AfriCode v5.0.0 application - ${projectName}`,
57
+ "type": "module",
58
+ "scripts": {
59
+ "dev": "bun run --hot src/server.ts",
60
+ "build": "bun build src/server.ts --outdir dist",
61
+ "start": "bun run dist/server.js",
62
+ "test": "bun test",
63
+ "lint": "eslint src --ext .ts,.tsx",
64
+ "generate": "africode generate a2ui"
65
+ },
66
+ "dependencies": {
67
+ "@africode/core": "^5.0.0"
68
+ },
69
+ "devDependencies": {
70
+ "@types/bun": "latest",
71
+ "eslint": "^9.0.0",
72
+ "typescript": "^5.0.0"
73
+ }
74
+ };
75
+
76
+ await writeFile(
77
+ path.join(projectDir, 'package.json'),
78
+ JSON.stringify(packageJson, null, 2)
79
+ );
80
+
81
+ // 3. Create src directory structure
82
+ await mkdir(path.join(projectDir, 'src'), { recursive: true });
83
+ await mkdir(path.join(projectDir, 'src', 'pages'), { recursive: true });
84
+ await mkdir(path.join(projectDir, 'src', 'components'), { recursive: true });
85
+ await mkdir(path.join(projectDir, 'src', 'core'), { recursive: true });
86
+ await mkdir(path.join(projectDir, 'public'), { recursive: true });
87
+
88
+ // 4. Create server.ts
89
+ const serverContent = `#!/usr/bin/env bun
90
+ /**
91
+ * ${projectName} - AfriCode v5.0.0 Server
92
+ */
93
+
94
+ import { AfriCode } from '@africode/core';
95
+
96
+ const app = new AfriCode({
97
+ port: 3000,
98
+ pagesDir: 'src/pages',
99
+ publicDir: 'public'
100
+ });
101
+
102
+ console.log('🚀 Starting ${projectName}...');
103
+ app.start();
104
+ `;
105
+
106
+ await writeFile(path.join(projectDir, 'src', 'server.ts'), serverContent);
107
+
108
+ // 5. Create index page
109
+ const indexPage = `/**
110
+ * Home Page - ${projectName}
111
+ */
112
+
113
+ import { html } from '@africode/core';
114
+
115
+ export default function Home() {
116
+ return html\`
117
+ <af-app>
118
+ <af-navbar>
119
+ <h1 slot="brand">${projectName}</h1>
120
+ </af-navbar>
121
+
122
+ <main>
123
+ <af-hero>
124
+ <h1>Welcome to ${projectName}</h1>
125
+ <p>Built with AfriCode v5.0.0</p>
126
+ <af-button variant="primary" size="lg">Get Started</af-button>
127
+ </af-hero>
128
+
129
+ <af-section>
130
+ <af-card>
131
+ <h2>African-Centric Framework</h2>
132
+ <p>Real-time performance with cultural patterns</p>
133
+ </af-card>
134
+ </af-section>
135
+ </main>
136
+ </af-app>
137
+ \`;
138
+ }
139
+ `;
140
+
141
+ await writeFile(path.join(projectDir, 'src', 'pages', 'index.ts'), indexPage);
142
+
143
+ // 6. Create tsconfig.json
144
+ const tsconfig = {
145
+ "compilerOptions": {
146
+ "target": "ES2022",
147
+ "module": "ESNext",
148
+ "moduleResolution": "bundler",
149
+ "allowSyntheticDefaultImports": true,
150
+ "esModuleInterop": true,
151
+ "jsx": "react-jsx",
152
+ "jsxImportSource": "@africode/core",
153
+ "strict": true,
154
+ "skipLibCheck": true,
155
+ "forceConsistentCasingInFileNames": true,
156
+ "resolveJsonModule": true,
157
+ "isolatedModules": true,
158
+ "noEmit": true,
159
+ "types": ["bun-types"]
160
+ },
161
+ "include": ["src/**/*"],
162
+ "exclude": ["node_modules", "dist"]
163
+ };
164
+
165
+ await writeFile(
166
+ path.join(projectDir, 'tsconfig.json'),
167
+ JSON.stringify(tsconfig, null, 2)
168
+ );
169
+
170
+ // 7. Create README
171
+ const readme = `# ${projectName}
172
+
173
+ Built with AfriCode v5.0.0 - African-centric full-stack framework.
174
+
175
+ ## Getting Started
176
+
177
+ \`\`\`bash
178
+ cd ${projectName}
179
+ bun install
180
+ bun run dev
181
+ \`\`\`
182
+
183
+ ## Features
184
+
185
+ - ⚡ Bun runtime with 5ms startup
186
+ - 🎨 African cultural patterns
187
+ - 🔐 Fintech compliance (NIDA, TIPS)
188
+ - 🤖 AI-safe component system
189
+ - 📱 Real-time WebSocket support
190
+
191
+ ## Scripts
192
+
193
+ - \`bun run dev\` - Start development server with hot reload
194
+ - \`bun run build\` - Build for production
195
+ - \`bun run start\` - Start production server
196
+ - \`bun run test\` - Run tests
197
+ - \`bun run generate\` - Generate A2UI schema
198
+ `;
199
+
200
+ await writeFile(path.join(projectDir, 'README.md'), readme);
201
+
202
+ console.log(`${colors.green}✓ Project "${projectName}" created successfully!${colors.reset}`);
203
+ console.log(`\n${colors.blue}Next steps:${colors.reset}`);
204
+ console.log(` cd ${projectName}`);
205
+ console.log(` bun install`);
206
+ console.log(` bun run dev`);
207
+ console.log(`\n${colors.dim}Happy coding with AfriCode! 🇹🇿${colors.reset}`);
208
+
209
+ } catch (error) {
210
+ console.error(`${colors.red}✗ Failed to create project: ${error.message}${colors.reset}`);
211
+ process.exit(1);
212
+ }
213
+ }
214
+
215
+ // CLI runner
216
+ if (import.meta.main) {
217
+ const projectName = process.argv[2];
218
+ createProject(projectName);
219
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * AfriCode Accordion Component
3
+ *
4
+ * Collapsible content sections for FAQs and organized information.
5
+ * Suitable for: FAQs, Settings, Documentation, Course content
6
+ *
7
+ * @module components/accordion
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+
12
+ export class AfriAccordion extends AfriCodeComponent {
13
+ static get observedAttributes() {
14
+ return ['theme', 'multiple'];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this._openItems = new Set();
20
+ this._uniqueId = Math.random().toString(36).substr(2, 9);
21
+ this.render();
22
+ }
23
+
24
+ connectedCallback() {
25
+ this._setupAccordion();
26
+ }
27
+
28
+ _setupAccordion() {
29
+ const headers = this.shadowRoot.querySelectorAll('.accordion-header');
30
+ headers.forEach((header, index) => {
31
+ header.addEventListener('click', () => {
32
+ this._toggleItem(index);
33
+ });
34
+ });
35
+ }
36
+
37
+ _toggleItem(index) {
38
+ const multiple = this.hasAttribute('multiple');
39
+
40
+ if (this._openItems.has(index)) {
41
+ this._openItems.delete(index);
42
+ } else {
43
+ if (!multiple) {
44
+ this._openItems.clear();
45
+ }
46
+ this._openItems.add(index);
47
+ }
48
+
49
+ this.render();
50
+ this._setupAccordion();
51
+ this.emit('af-toggle', { index, open: this._openItems.has(index) });
52
+ }
53
+
54
+ attributeChangedCallback() {
55
+ this.render();
56
+ this._setupAccordion();
57
+ }
58
+
59
+ render() {
60
+ const theme = this.getAttribute('theme') || 'tanzania';
61
+ const itemsSlot = this.querySelector('[slot="items"]');
62
+ const items = itemsSlot ? Array.from(itemsSlot.children) : [];
63
+
64
+ const themes = {
65
+ tanzania: { accent: '#1EB53A' },
66
+ maasai: { accent: '#FF0000' },
67
+ ndebele: { accent: '#4169E1' }
68
+ };
69
+ const t = themes[theme] || themes.tanzania;
70
+
71
+ this.shadowRoot.innerHTML = `
72
+ <style>
73
+ :host {
74
+ display: block;
75
+ font-family: 'Inter', system-ui, sans-serif;
76
+ }
77
+
78
+ .accordion {
79
+ border-radius: 8px;
80
+ overflow: hidden;
81
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
82
+ }
83
+
84
+ .accordion-item {
85
+ border-bottom: 1px solid #eee;
86
+ }
87
+
88
+ .accordion-item:last-child {
89
+ border-bottom: none;
90
+ }
91
+
92
+ .accordion-header {
93
+ width: 100%;
94
+ padding: 16px 21px;
95
+ background: white;
96
+ border: none;
97
+ text-align: left;
98
+ font-family: inherit;
99
+ font-size: 15px;
100
+ font-weight: 600;
101
+ color: #333;
102
+ cursor: pointer;
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ transition: all 200ms ease;
107
+ }
108
+
109
+ .accordion-header:hover {
110
+ background: #f8f9fa;
111
+ }
112
+
113
+ .accordion-header.active {
114
+ color: ${t.accent};
115
+ }
116
+
117
+ .accordion-icon {
118
+ font-size: 12px;
119
+ transition: transform 300ms ease;
120
+ }
121
+
122
+ .accordion-header.active .accordion-icon {
123
+ transform: rotate(180deg);
124
+ }
125
+
126
+ .accordion-content {
127
+ max-height: 0;
128
+ overflow: hidden;
129
+ background: #fafafa;
130
+ transition: max-height 300ms ease-out;
131
+ }
132
+
133
+ .accordion-content.open {
134
+ max-height: 500px;
135
+ }
136
+
137
+ .accordion-body {
138
+ padding: 16px 21px;
139
+ color: #555;
140
+ line-height: 1.6;
141
+ }
142
+
143
+ ::slotted([slot="items"]) {
144
+ display: none;
145
+ }
146
+ </style>
147
+
148
+ <div class="accordion">
149
+ ${items.map((item, i) => {
150
+ const expanded = this._openItems.has(i);
151
+ const headerId = `accordion-header-${this._uniqueId}-${i}`;
152
+ const panelId = `accordion-panel-${this._uniqueId}-${i}`;
153
+ return `
154
+ <div class="accordion-item">
155
+ <button
156
+ class="accordion-header ${expanded ? 'active' : ''}"
157
+ aria-expanded="${expanded}"
158
+ aria-controls="${panelId}"
159
+ id="${headerId}"
160
+ >
161
+ <span>${item.getAttribute('title') || `Item ${i + 1}`}</span>
162
+ <span class="accordion-icon">▼</span>
163
+ </button>
164
+ <div
165
+ class="accordion-content ${expanded ? 'open' : ''}"
166
+ id="${panelId}"
167
+ role="region"
168
+ aria-labelledby="${headerId}"
169
+ >
170
+ <div class="accordion-body">
171
+ ${item.innerHTML}
172
+ </div>
173
+ </div>
174
+ </div>
175
+ `}).join('')}
176
+ </div>
177
+ <slot name="items"></slot>
178
+ `;
179
+ }
180
+ }
181
+
182
+ registerComponent('af-accordion', AfriAccordion);
183
+ export default AfriAccordion;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * AfriCode Alert/Notification Component
3
+ *
4
+ * Status messages, alerts, and notifications with cultural theming.
5
+ * Suitable for: Healthcare alerts, Financial notifications, System messages
6
+ *
7
+ * @module components/alert
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+
12
+ export class AfriAlert extends AfriCodeComponent {
13
+ static get observedAttributes() {
14
+ return ['type', 'title', 'dismissible'];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this.render();
20
+ }
21
+
22
+ connectedCallback() {
23
+ this.shadowRoot.querySelector('.close-btn')?.addEventListener('click', () => {
24
+ this.remove();
25
+ this.emit('af-dismiss');
26
+ });
27
+ }
28
+
29
+ attributeChangedCallback() {
30
+ this.render();
31
+ }
32
+
33
+ render() {
34
+ const type = this.getAttribute('type') || 'info';
35
+ const title = this.getAttribute('title') || '';
36
+ const dismissible = this.hasAttribute('dismissible');
37
+
38
+ const types = {
39
+ success: { bg: '#d4edda', border: '#1EB53A', text: '#155724', icon: '✓' },
40
+ warning: { bg: '#fff3cd', border: '#FCD116', text: '#856404', icon: '⚠' },
41
+ error: { bg: '#f8d7da', border: '#DC3545', text: '#721c24', icon: '✕' },
42
+ info: { bg: '#d1ecf1', border: '#00A3DD', text: '#0c5460', icon: 'ℹ' }
43
+ };
44
+ const t = types[type] || types.info;
45
+
46
+ this.shadowRoot.innerHTML = `
47
+ <style>
48
+ :host {
49
+ display: block;
50
+ font-family: 'Inter', system-ui, sans-serif;
51
+ }
52
+
53
+ .alert {
54
+ padding: 13px 21px;
55
+ border-radius: 5px;
56
+ border-left: 4px solid ${t.border};
57
+ background: ${t.bg};
58
+ color: ${t.text};
59
+ display: flex;
60
+ align-items: flex-start;
61
+ gap: 13px;
62
+ animation: slideIn 300ms ease-out;
63
+ }
64
+
65
+ @keyframes slideIn {
66
+ from {
67
+ opacity: 0;
68
+ transform: translateY(-10px);
69
+ }
70
+ to {
71
+ opacity: 1;
72
+ transform: translateY(0);
73
+ }
74
+ }
75
+
76
+ .icon {
77
+ font-size: 18px;
78
+ flex-shrink: 0;
79
+ width: 24px;
80
+ height: 24px;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ background: ${t.border};
85
+ color: white;
86
+ border-radius: 50%;
87
+ }
88
+
89
+ .content {
90
+ flex: 1;
91
+ }
92
+
93
+ .title {
94
+ font-weight: 600;
95
+ margin-bottom: 3px;
96
+ }
97
+
98
+ .close-btn {
99
+ background: none;
100
+ border: none;
101
+ font-size: 18px;
102
+ cursor: pointer;
103
+ color: ${t.text};
104
+ opacity: 0.7;
105
+ padding: 0;
106
+ line-height: 1;
107
+ }
108
+
109
+ .close-btn:hover {
110
+ opacity: 1;
111
+ }
112
+
113
+ ::slotted(*) {
114
+ margin: 0;
115
+ }
116
+ </style>
117
+
118
+ <div class="alert" role="alert">
119
+ <span class="icon">${t.icon}</span>
120
+ <div class="content">
121
+ ${title ? `<div class="title">${title}</div>` : ''}
122
+ <slot></slot>
123
+ </div>
124
+ ${dismissible ? '<button class="close-btn" aria-label="Dismiss">×</button>' : ''}
125
+ </div>
126
+ `;
127
+ }
128
+ }
129
+
130
+ registerComponent('af-alert', AfriAlert);
131
+ export default AfriAlert;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * AfriCode Auth Component
3
+ * A "Profound Dark" themed login/register form.
4
+ *
5
+ * Usage: <af-auth></af-auth>
6
+ */
7
+
8
+ import { AfriCodeComponent, registerComponent } from './base.js';
9
+
10
+ export class AfriAuth extends AfriCodeComponent {
11
+ constructor() {
12
+ super();
13
+ this.mode = 'login'; // login | register
14
+ }
15
+
16
+ connectedCallback() {
17
+ this.render();
18
+ this.setupEvents();
19
+ }
20
+
21
+ setupEvents() {
22
+ const form = this.shadowRoot.querySelector('form');
23
+ const toggle = this.shadowRoot.querySelector('.toggle-mode');
24
+
25
+ if (toggle) {
26
+ toggle.addEventListener('click', (e) => {
27
+ e.preventDefault();
28
+ this.mode = this.mode === 'login' ? 'register' : 'login';
29
+ this.render();
30
+ this.setupEvents();
31
+ });
32
+ }
33
+
34
+ if (form) {
35
+ form.addEventListener('submit', async (e) => {
36
+ e.preventDefault();
37
+ const email = this.shadowRoot.querySelector('#email').value;
38
+ const password = this.shadowRoot.querySelector('#password').value;
39
+ const feedback = this.shadowRoot.querySelector('.feedback');
40
+
41
+ feedback.textContent = 'Processing...';
42
+
43
+ try {
44
+ const res = await fetch('/api/auth', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({ action: this.mode, email, password })
48
+ });
49
+
50
+ const data = await res.json();
51
+
52
+ if (data.error) {
53
+ feedback.textContent = data.error;
54
+ feedback.style.color = '#ef4444';
55
+ } else {
56
+ feedback.textContent = 'Success! Redirecting...';
57
+ feedback.style.color = '#22c55e';
58
+ setTimeout(() => window.location.href = '/', 1000);
59
+ }
60
+ } catch (err) {
61
+ feedback.textContent = 'Network Error';
62
+ }
63
+ });
64
+ }
65
+ }
66
+
67
+ render() {
68
+ const isLogin = this.mode === 'login';
69
+
70
+ this.shadowRoot.innerHTML = `
71
+ <style>
72
+ :host {
73
+ display: block;
74
+ font-family: 'Inter', system-ui;
75
+ max-width: 400px;
76
+ margin: 0 auto;
77
+ }
78
+ .auth-card {
79
+ background: #1e293b;
80
+ border: 1px solid #334155;
81
+ border-radius: 12px;
82
+ padding: 32px;
83
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
84
+ }
85
+ h2 {
86
+ margin-top: 0;
87
+ color: white;
88
+ text-align: center;
89
+ font-weight: 800;
90
+ }
91
+ .input-group {
92
+ margin-bottom: 20px;
93
+ }
94
+ label {
95
+ display: block;
96
+ color: #94a3b8;
97
+ font-size: 0.9rem;
98
+ margin-bottom: 8px;
99
+ }
100
+ input {
101
+ width: 100%;
102
+ padding: 12px;
103
+ background: #0f172a;
104
+ border: 1px solid #334155;
105
+ border-radius: 6px;
106
+ color: white;
107
+ font-size: 1rem;
108
+ box-sizing: border-box;
109
+ }
110
+ input:focus {
111
+ border-color: #1EB53A;
112
+ outline: none;
113
+ }
114
+ button {
115
+ width: 100%;
116
+ padding: 12px;
117
+ background: #1EB53A;
118
+ color: white;
119
+ border: none;
120
+ border-radius: 6px;
121
+ font-weight: 700;
122
+ cursor: pointer;
123
+ font-size: 1rem;
124
+ transition: all 0.2s;
125
+ }
126
+ button:hover {
127
+ opacity: 0.9;
128
+ transform: translateY(-1px);
129
+ }
130
+ .footer {
131
+ margin-top: 20px;
132
+ text-align: center;
133
+ font-size: 0.9rem;
134
+ color: #94a3b8;
135
+ }
136
+ .toggle-mode {
137
+ color: #FCD116;
138
+ cursor: pointer;
139
+ text-decoration: underline;
140
+ }
141
+ .feedback {
142
+ text-align: center;
143
+ margin-bottom: 16px;
144
+ min-height: 20px;
145
+ font-size: 0.9rem;
146
+ }
147
+ </style>
148
+
149
+ <div class="auth-card">
150
+ <h2>${isLogin ? 'Welcome Back' : 'Create Account'}</h2>
151
+ <div class="feedback"></div>
152
+ <form>
153
+ <div class="input-group">
154
+ <label for="email">Email Address</label>
155
+ <input type="email" id="email" required placeholder="you@example.com">
156
+ </div>
157
+ <div class="input-group">
158
+ <label for="password">Password</label>
159
+ <input type="password" id="password" required placeholder="••••••••">
160
+ </div>
161
+ <button type="submit">${isLogin ? 'Sign In' : 'Sign Up'}</button>
162
+ </form>
163
+ <div class="footer">
164
+ ${isLogin ? 'New here?' : 'Already have an account?'}
165
+ <span class="toggle-mode">${isLogin ? 'Create an account' : 'Sign in'}</span>
166
+ </div>
167
+ </div>
168
+ `;
169
+ }
170
+ }
171
+
172
+ registerComponent('af-auth', AfriAuth);