@oasisomniverse/angular 1.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.
- package/package.json +37 -0
- package/src/components/avatar/avatar-wallet.component.ts +49 -0
- package/src/components/avatar/edit-avatar.component.ts +28 -0
- package/src/components/avatar/search-avatar.component.ts +45 -0
- package/src/components/avatar/view-avatar.component.ts +49 -0
- package/src/components/avatar-connect/avatar-connect.component.ts +78 -0
- package/src/components/avatar-profile/avatar-profile.component.ts +146 -0
- package/src/components/coming-soon/coming-soon.component.ts +27 -0
- package/src/components/confirmation/confirmation.component.ts +38 -0
- package/src/components/contact/contact.component.ts +64 -0
- package/src/components/data-screen/add-data.component.ts +50 -0
- package/src/components/data-screen/cross-chain-management.component.ts +28 -0
- package/src/components/data-screen/load-data.component.ts +28 -0
- package/src/components/data-screen/manage-data.component.ts +50 -0
- package/src/components/data-screen/off-chain-management.component.ts +28 -0
- package/src/components/data-screen/search-data.component.ts +40 -0
- package/src/components/eggs/eggs.component.ts +76 -0
- package/src/components/eggs/manage-eggs.component.ts +50 -0
- package/src/components/eggs/search-eggs.component.ts +40 -0
- package/src/components/eggs/view-eggs.component.ts +49 -0
- package/src/components/forgot-password/forgot-password.component.ts +80 -0
- package/src/components/game/game.component.ts +58 -0
- package/src/components/game/search-profiles.component.ts +36 -0
- package/src/components/game/view-achievements.component.ts +48 -0
- package/src/components/game/view-leagues.component.ts +48 -0
- package/src/components/game/view-tournaments.component.ts +49 -0
- package/src/components/hyperdrive/hyperdrive.component.ts +81 -0
- package/src/components/karma/search-karma.component.ts +36 -0
- package/src/components/karma/view-avatar-karma.component.ts +51 -0
- package/src/components/karma/view-karma.component.ts +46 -0
- package/src/components/karma/vote-karma.component.ts +48 -0
- package/src/components/karma-panel/karma-panel.component.ts +87 -0
- package/src/components/karma-toast/karma-toast.component.ts +33 -0
- package/src/components/login/login.component.ts +95 -0
- package/src/components/map/add-2d-object-map.component.ts +42 -0
- package/src/components/map/add-3d-object-map.component.ts +43 -0
- package/src/components/map/add-quest-to-map.component.ts +42 -0
- package/src/components/map/download-our-world.component.ts +28 -0
- package/src/components/map/manage-map.component.ts +28 -0
- package/src/components/map/map.component.ts +70 -0
- package/src/components/map/plot-route-on-map.component.ts +42 -0
- package/src/components/map/search-map.component.ts +44 -0
- package/src/components/map/view-global-3d-map.component.ts +28 -0
- package/src/components/map/view-halons-on-map.component.ts +28 -0
- package/src/components/map/view-oapp-on-map.component.ts +28 -0
- package/src/components/map/view-quest-on-map.component.ts +28 -0
- package/src/components/menu-message/menu-message.component.ts +69 -0
- package/src/components/messages/message.component.ts +59 -0
- package/src/components/messaging/messaging.component.ts +89 -0
- package/src/components/mission/manage-mission.component.ts +43 -0
- package/src/components/mission/mission.component.ts +63 -0
- package/src/components/mission/search-mission.component.ts +36 -0
- package/src/components/mission/view-mission.component.ts +49 -0
- package/src/components/modal/modal.component.ts +28 -0
- package/src/components/navbar/navbar.component.ts +64 -0
- package/src/components/nft/contact-popup.component.ts +41 -0
- package/src/components/nft/manage-oasis-nft.component.ts +43 -0
- package/src/components/nft/nft.component.ts +70 -0
- package/src/components/nft/purchase-oasis-nft.component.ts +53 -0
- package/src/components/nft/purchase-oasis-virtual-land-nft.component.ts +28 -0
- package/src/components/nft/search-oasis-nft.component.ts +36 -0
- package/src/components/nft/solana.component.ts +28 -0
- package/src/components/nft/view-oasis-nft.component.ts +52 -0
- package/src/components/oapp/create-oapp.component.ts +43 -0
- package/src/components/oapp/deploy-oapp.component.ts +42 -0
- package/src/components/oapp/download-our-world.component.ts +28 -0
- package/src/components/oapp/edit-oapp.component.ts +41 -0
- package/src/components/oapp/install-oapp.component.ts +36 -0
- package/src/components/oapp/launch-oapp.component.ts +58 -0
- package/src/components/oapp/manage-oapp.component.ts +42 -0
- package/src/components/oapp/oapp.component.ts +70 -0
- package/src/components/oapp/search-oapp.component.ts +36 -0
- package/src/components/oasis-card/oasis-card.component.ts +30 -0
- package/src/components/onet/onet.component.ts +77 -0
- package/src/components/onode/onode.component.ts +73 -0
- package/src/components/provider/activity-pub.component.ts +44 -0
- package/src/components/provider/compare-provider-speeds.component.ts +48 -0
- package/src/components/provider/eosio.component.ts +44 -0
- package/src/components/provider/ethereum.component.ts +44 -0
- package/src/components/provider/holochain.component.ts +44 -0
- package/src/components/provider/ipfs.component.ts +44 -0
- package/src/components/provider/manage-auto-fail-over.component.ts +42 -0
- package/src/components/provider/manage-auto-replicaton.component.ts +42 -0
- package/src/components/provider/manage-load-balancing.component.ts +42 -0
- package/src/components/provider/manage-providers.component.ts +28 -0
- package/src/components/provider/mongo-db.component.ts +44 -0
- package/src/components/provider/neo4j.component.ts +44 -0
- package/src/components/provider/search-providers.component.ts +36 -0
- package/src/components/provider/seeds-provider.component.ts +42 -0
- package/src/components/provider/solid.component.ts +44 -0
- package/src/components/provider/sql-lite.component.ts +44 -0
- package/src/components/provider/three-fold.component.ts +44 -0
- package/src/components/provider/view-provider-stats.component.ts +61 -0
- package/src/components/provider/view-providers.component.ts +54 -0
- package/src/components/provider-dropdown/provider-dropdown.component.ts +64 -0
- package/src/components/providers/providers.component.ts +60 -0
- package/src/components/quest/manage-quest.component.ts +43 -0
- package/src/components/quest/quest.component.ts +65 -0
- package/src/components/quest/search-quest.component.ts +36 -0
- package/src/components/quest/view-quest.component.ts +49 -0
- package/src/components/reset-password/reset-password.component.ts +68 -0
- package/src/components/search-avatars/search-avatars.component.ts +80 -0
- package/src/components/seeds/accept-invite.component.ts +44 -0
- package/src/components/seeds/donate-seeds.component.ts +42 -0
- package/src/components/seeds/manage-seeds.component.ts +42 -0
- package/src/components/seeds/pay-with-seeds.component.ts +41 -0
- package/src/components/seeds/reward-seeds.component.ts +41 -0
- package/src/components/seeds/search-seeds.component.ts +36 -0
- package/src/components/seeds/seeds.component.ts +75 -0
- package/src/components/seeds/send-invite.component.ts +41 -0
- package/src/components/seeds/view-organizations.component.ts +55 -0
- package/src/components/seeds/view-seeds.component.ts +78 -0
- package/src/components/settings/settings.component.ts +96 -0
- package/src/components/signup/signup.component.ts +118 -0
- package/src/components/star-field/star-field.component.ts +44 -0
- package/src/components/verify-email/verify-email.component.ts +48 -0
- package/src/components/wallet/wallet.component.ts +83 -0
- package/src/index.ts +145 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-view-leagues',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="lg-shell">
|
|
10
|
+
<div class="lg-header"><span>🏅</span><h2 class="lg-title">Leagues</h2></div>
|
|
11
|
+
<div class="lg-table-wrap">
|
|
12
|
+
<table class="lg-table">
|
|
13
|
+
<thead><tr><th>Rank</th><th>League</th><th>Members</th><th>Season Points</th><th>Status</th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
@for (l of leagues(); track l.rank) {
|
|
16
|
+
<tr><td>{{ l.rank }}</td><td>{{ l.name }}</td><td>{{ l.members }}</td><td>{{ l.points }}</td><td><span class="lg-status lg-status--{{ l.status.toLowerCase() }}">{{ l.status }}</span></td></tr>
|
|
17
|
+
}
|
|
18
|
+
</tbody>
|
|
19
|
+
</table>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="lg-footer"><button class="lg-btn" (click)="close.emit()">Close</button></div>
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
styles: [`
|
|
25
|
+
.lg-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
|
|
26
|
+
.lg-header{display:flex;align-items:center;gap:10px}
|
|
27
|
+
.lg-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
28
|
+
.lg-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
|
|
29
|
+
.lg-table{width:100%;border-collapse:collapse;font-size:13px}
|
|
30
|
+
.lg-table th{background:rgba(0,200,255,.08);color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:10px;font-weight:700;letter-spacing:.06em;padding:10px 12px;text-align:left;border-bottom:1px solid rgba(0,200,255,.15)}
|
|
31
|
+
.lg-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
|
|
32
|
+
.lg-status{border-radius:999px;padding:2px 9px;font-size:10px;font-weight:700}
|
|
33
|
+
.lg-status--active{background:rgba(0,200,100,.15);color:#00c864;border:1px solid rgba(0,200,100,.3)}
|
|
34
|
+
.lg-status--closed{background:rgba(122,155,191,.1);color:#7a9bbf;border:1px solid rgba(122,155,191,.25)}
|
|
35
|
+
.lg-footer{display:flex;justify-content:flex-end}
|
|
36
|
+
.lg-btn{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:9px 22px;cursor:pointer}
|
|
37
|
+
`]
|
|
38
|
+
})
|
|
39
|
+
export class ViewLeaguesComponent {
|
|
40
|
+
@Input() show = false;
|
|
41
|
+
@Output() close = new EventEmitter<void>();
|
|
42
|
+
leagues = signal([
|
|
43
|
+
{ rank:1, name:'Platinum League', members:128, points:9800, status:'Active' },
|
|
44
|
+
{ rank:2, name:'Gold League', members:256, points:6500, status:'Active' },
|
|
45
|
+
{ rank:3, name:'Silver League', members:512, points:3200, status:'Active' },
|
|
46
|
+
{ rank:4, name:'Bronze League', members:1024, points:1100, status:'Closed' },
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-view-tournaments',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="tn-shell">
|
|
10
|
+
<div class="tn-header"><span>⚔️</span><h2 class="tn-title">Tournaments</h2></div>
|
|
11
|
+
<div class="tn-list">
|
|
12
|
+
@for (t of tournaments(); track t.id) {
|
|
13
|
+
<div class="tn-card">
|
|
14
|
+
<div class="tn-status tn-status--{{ t.status.toLowerCase() }}">{{ t.status }}</div>
|
|
15
|
+
<div class="tn-name">{{ t.name }}</div>
|
|
16
|
+
<div class="tn-meta">{{ t.participants }} participants · Prize: {{ t.prize }}</div>
|
|
17
|
+
<div class="tn-date">{{ t.date }}</div>
|
|
18
|
+
</div>
|
|
19
|
+
}
|
|
20
|
+
</div>
|
|
21
|
+
<div class="tn-footer"><button class="tn-btn" (click)="close.emit()">Close</button></div>
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
styles: [`
|
|
25
|
+
.tn-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
|
|
26
|
+
.tn-header{display:flex;align-items:center;gap:10px}
|
|
27
|
+
.tn-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
28
|
+
.tn-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
|
|
29
|
+
.tn-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.15);border-radius:10px;padding:16px;display:flex;flex-direction:column;gap:8px}
|
|
30
|
+
.tn-status{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;border-radius:999px;padding:2px 10px;align-self:flex-start;border:1px solid currentColor}
|
|
31
|
+
.tn-status--open{color:#00c864;border-color:rgba(0,200,100,.3)}
|
|
32
|
+
.tn-status--upcoming{color:#5ba8ff;border-color:rgba(91,168,255,.3)}
|
|
33
|
+
.tn-status--closed{color:#7a9bbf;border-color:rgba(122,155,191,.25)}
|
|
34
|
+
.tn-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff}
|
|
35
|
+
.tn-meta{font-size:12px;color:#7a9bbf}
|
|
36
|
+
.tn-date{font-size:11px;color:#5ba8ff}
|
|
37
|
+
.tn-footer{display:flex;justify-content:flex-end}
|
|
38
|
+
.tn-btn{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:9px 22px;cursor:pointer}
|
|
39
|
+
`]
|
|
40
|
+
})
|
|
41
|
+
export class ViewTournamentsComponent {
|
|
42
|
+
@Input() show = false;
|
|
43
|
+
@Output() close = new EventEmitter<void>();
|
|
44
|
+
tournaments = signal([
|
|
45
|
+
{ id:1, name:'Galactic Cup 2026', participants:64, prize:'500 SEEDS', status:'Open', date:'2026-07-15' },
|
|
46
|
+
{ id:2, name:'Neon Arena Finals', participants:32, prize:'1 ETH', status:'Upcoming', date:'2026-08-01' },
|
|
47
|
+
{ id:3, name:'Star Wars Classic', participants:128, prize:'200 SEEDS', status:'Closed', date:'2026-06-01' },
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-hyperdrive',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="hd-shell">
|
|
10
|
+
<div class="hd-header"><h2 class="hd-title">🚀 HyperDrive</h2><p class="hd-sub">Quantum-speed data layer for the OASIS Omniverse.</p></div>
|
|
11
|
+
<div class="hd-status-card" [class.hd-status-card--active]="active()">
|
|
12
|
+
<div class="hd-status-icon">{{ active() ? '⚡' : '💤' }}</div>
|
|
13
|
+
<div class="hd-status-info">
|
|
14
|
+
<div class="hd-status-label">HyperDrive Status</div>
|
|
15
|
+
<div class="hd-status-val" [class.hd-status-val--on]="active()">{{ active() ? 'ACTIVE' : 'OFFLINE' }}</div>
|
|
16
|
+
</div>
|
|
17
|
+
<button class="hd-toggle-btn" [class.hd-toggle-btn--on]="active()" (click)="active.set(!active())">{{ active() ? 'Disengage' : 'Engage' }}</button>
|
|
18
|
+
</div>
|
|
19
|
+
@if (active()) {
|
|
20
|
+
<div class="hd-metrics">
|
|
21
|
+
@for (m of metrics; track m.label) {
|
|
22
|
+
<div class="hd-metric-card">
|
|
23
|
+
<div class="hd-metric-val">{{ m.value }}</div>
|
|
24
|
+
<div class="hd-metric-label">{{ m.label }}</div>
|
|
25
|
+
</div>
|
|
26
|
+
}
|
|
27
|
+
</div>
|
|
28
|
+
<div class="hd-nodes">
|
|
29
|
+
<div class="hd-nodes-header">Connected Nodes</div>
|
|
30
|
+
@for (n of nodes; track n.id) {
|
|
31
|
+
<div class="hd-node">
|
|
32
|
+
<div class="hd-node-dot hd-node-dot--{{ n.status }}"></div>
|
|
33
|
+
<div class="hd-node-name">{{ n.name }}</div>
|
|
34
|
+
<div class="hd-node-latency">{{ n.latency }}ms</div>
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
</div>
|
|
38
|
+
}
|
|
39
|
+
</div>
|
|
40
|
+
`,
|
|
41
|
+
styles: [`
|
|
42
|
+
.hd-shell{display:flex;flex-direction:column;gap:18px}
|
|
43
|
+
.hd-header{text-align:center}
|
|
44
|
+
.hd-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
|
|
45
|
+
.hd-sub{font-size:13px;color:#7a9bbf;margin:0}
|
|
46
|
+
.hd-status-card{display:flex;align-items:center;gap:14px;background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:12px;padding:16px;transition:border-color .2s}
|
|
47
|
+
.hd-status-card--active{border-color:rgba(0,200,255,.4);background:rgba(0,200,255,.05)}
|
|
48
|
+
.hd-status-icon{font-size:28px}
|
|
49
|
+
.hd-status-info{flex:1}
|
|
50
|
+
.hd-status-label{font-size:12px;color:#7a9bbf;text-transform:uppercase;letter-spacing:.06em}
|
|
51
|
+
.hd-status-val{font-family:'Orbitron',sans-serif;font-size:16px;color:#7a9bbf;margin-top:2px}
|
|
52
|
+
.hd-status-val--on{color:#00c8ff}
|
|
53
|
+
.hd-toggle-btn{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.08em;padding:10px 20px;cursor:pointer;transition:opacity .2s}
|
|
54
|
+
.hd-toggle-btn--on{background:rgba(255,80,80,.2);border:1px solid rgba(255,80,80,.35);color:#ff6b6b}
|
|
55
|
+
.hd-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
|
|
56
|
+
.hd-metric-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.1);border-radius:10px;padding:14px;text-align:center}
|
|
57
|
+
.hd-metric-val{font-family:'Share Tech Mono',monospace;font-size:20px;color:#00c8ff;font-weight:600}
|
|
58
|
+
.hd-metric-label{font-size:11px;color:#7a9bbf;margin-top:4px;text-transform:uppercase;letter-spacing:.06em}
|
|
59
|
+
.hd-nodes{display:flex;flex-direction:column;gap:8px}
|
|
60
|
+
.hd-nodes-header{font-family:'Orbitron',sans-serif;font-size:12px;color:#7a9bbf;letter-spacing:.06em;text-transform:uppercase}
|
|
61
|
+
.hd-node{display:flex;align-items:center;gap:10px;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid rgba(0,200,255,.07);border-radius:8px}
|
|
62
|
+
.hd-node-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
63
|
+
.hd-node-dot--active{background:#48dc82;box-shadow:0 0 6px #48dc82}
|
|
64
|
+
.hd-node-dot--idle{background:#7a9bbf}
|
|
65
|
+
.hd-node-name{flex:1;font-size:13px;color:#e0f0ff}
|
|
66
|
+
.hd-node-latency{font-family:'Share Tech Mono',monospace;font-size:12px;color:#7a9bbf}
|
|
67
|
+
`]
|
|
68
|
+
})
|
|
69
|
+
export class HyperDriveComponent {
|
|
70
|
+
active = signal(false);
|
|
71
|
+
metrics = [
|
|
72
|
+
{ label: 'Throughput', value: '4.2 TB/s' },
|
|
73
|
+
{ label: 'Latency', value: '0.3ms' },
|
|
74
|
+
{ label: 'Uptime', value: '99.98%' },
|
|
75
|
+
];
|
|
76
|
+
nodes = [
|
|
77
|
+
{ id: '1', name: 'ONODE Alpha — US East', status: 'active', latency: 12 },
|
|
78
|
+
{ id: '2', name: 'ONODE Beta — EU West', status: 'active', latency: 28 },
|
|
79
|
+
{ id: '3', name: 'ONODE Gamma — Asia Pacific', status: 'idle', latency: 55 },
|
|
80
|
+
];
|
|
81
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'oasis-search-karma',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule, FormsModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="sf-shell">
|
|
11
|
+
<div class="sf-header"><span>✨</span><h2 class="sf-title">Search Karma</h2></div>
|
|
12
|
+
<div class="sf-row"><input class="sf-input" [(ngModel)]="query" placeholder="Search by avatar name..." (keyup.enter)="search()" /><button class="sf-btn" (click)="search()">Search</button></div>
|
|
13
|
+
@for (r of results(); track r) { <div class="sf-result">{{ r }}</div> }
|
|
14
|
+
<div class="sf-footer"><button class="sf-cancel" (click)="close.emit()">Close</button></div>
|
|
15
|
+
</div>
|
|
16
|
+
`,
|
|
17
|
+
styles: [`
|
|
18
|
+
.sf-shell{display:flex;flex-direction:column;gap:14px;padding:20px}
|
|
19
|
+
.sf-header{display:flex;align-items:center;gap:10px}
|
|
20
|
+
.sf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
21
|
+
.sf-row{display:flex;gap:10px}
|
|
22
|
+
.sf-input{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
|
|
23
|
+
.sf-input:focus{border-color:rgba(0,200,255,.6)}
|
|
24
|
+
.sf-btn{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 20px;cursor:pointer;white-space:nowrap}
|
|
25
|
+
.sf-result{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:8px;color:#c8dff0;font-size:13px;padding:10px 14px}
|
|
26
|
+
.sf-footer{display:flex;justify-content:flex-end}
|
|
27
|
+
.sf-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:8px 20px;cursor:pointer}
|
|
28
|
+
`]
|
|
29
|
+
})
|
|
30
|
+
export class SearchKarmaComponent {
|
|
31
|
+
@Input() show = false;
|
|
32
|
+
@Output() close = new EventEmitter<void>();
|
|
33
|
+
query = '';
|
|
34
|
+
results = signal<string[]>([]);
|
|
35
|
+
search() { if (this.query.trim()) this.results.set(['No results found for "' + this.query + '"']); }
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-view-avatar-karma',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="vak-shell">
|
|
10
|
+
<div class="vak-header"><span>⭐</span><h2 class="vak-title">Avatar Karma</h2></div>
|
|
11
|
+
<div class="vak-score">
|
|
12
|
+
<div class="vak-score-val">{{ totalKarma() }}</div>
|
|
13
|
+
<div class="vak-score-lbl">Total Karma Points</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="vak-breakdown">
|
|
16
|
+
@for (k of karmaBreakdown(); track k.category) {
|
|
17
|
+
<div class="vak-row"><span class="vak-cat">{{ k.category }}</span><div class="vak-bar-wrap"><div class="vak-bar" [style.width.%]="k.pct"></div></div><span class="vak-pts">{{ k.points }}</span></div>
|
|
18
|
+
}
|
|
19
|
+
</div>
|
|
20
|
+
<div class="vak-footer"><button class="vak-btn" (click)="close.emit()">Close</button></div>
|
|
21
|
+
</div>
|
|
22
|
+
`,
|
|
23
|
+
styles: [`
|
|
24
|
+
.vak-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
|
|
25
|
+
.vak-header{display:flex;align-items:center;gap:10px}
|
|
26
|
+
.vak-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
27
|
+
.vak-score{text-align:center;padding:20px;background:rgba(255,180,60,.06);border:1px solid rgba(255,180,60,.2);border-radius:12px}
|
|
28
|
+
.vak-score-val{font-family:'Orbitron',sans-serif;font-size:40px;font-weight:700;color:#ffb43c}
|
|
29
|
+
.vak-score-lbl{font-size:12px;color:#7a9bbf;margin-top:4px;text-transform:uppercase;letter-spacing:.08em}
|
|
30
|
+
.vak-breakdown{display:flex;flex-direction:column;gap:10px}
|
|
31
|
+
.vak-row{display:flex;align-items:center;gap:12px}
|
|
32
|
+
.vak-cat{font-size:12px;color:#c8dff0;width:140px;flex-shrink:0}
|
|
33
|
+
.vak-bar-wrap{flex:1;height:6px;background:rgba(255,255,255,.08);border-radius:999px;overflow:hidden}
|
|
34
|
+
.vak-bar{height:100%;background:linear-gradient(90deg,#ffb43c,#ff8c00);border-radius:999px;transition:width .4s}
|
|
35
|
+
.vak-pts{font-size:12px;color:#ffb43c;font-weight:700;width:40px;text-align:right}
|
|
36
|
+
.vak-footer{display:flex;justify-content:flex-end}
|
|
37
|
+
.vak-btn{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:9px 22px;cursor:pointer}
|
|
38
|
+
`]
|
|
39
|
+
})
|
|
40
|
+
export class ViewAvatarKarmaComponent {
|
|
41
|
+
@Input() show = false;
|
|
42
|
+
@Output() close = new EventEmitter<void>();
|
|
43
|
+
totalKarma = signal(840);
|
|
44
|
+
karmaBreakdown = signal([
|
|
45
|
+
{ category: 'Quest Completion', points: 300, pct: 80 },
|
|
46
|
+
{ category: 'Helping Others', points: 200, pct: 55 },
|
|
47
|
+
{ category: 'NFT Minting', points: 150, pct: 40 },
|
|
48
|
+
{ category: 'Community Votes', points: 120, pct: 32 },
|
|
49
|
+
{ category: 'Seeds Donated', points: 70, pct: 19 },
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-view-karma',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="vk-shell">
|
|
10
|
+
<div class="vk-header"><span>✨</span><h2 class="vk-title">View Karma</h2></div>
|
|
11
|
+
<div class="vk-table-wrap">
|
|
12
|
+
<table class="vk-table">
|
|
13
|
+
<thead><tr><th>Avatar</th><th>Karma</th><th>Level</th><th>Rank</th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
@for (k of karmaList(); track k.avatar) {
|
|
16
|
+
<tr><td>{{ k.avatar }}</td><td>{{ k.karma }}</td><td>{{ k.level }}</td><td>#{{ k.rank }}</td></tr>
|
|
17
|
+
}
|
|
18
|
+
</tbody>
|
|
19
|
+
</table>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="vk-footer"><button class="vk-btn" (click)="close.emit()">Close</button></div>
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
styles: [`
|
|
25
|
+
.vk-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
|
|
26
|
+
.vk-header{display:flex;align-items:center;gap:10px}
|
|
27
|
+
.vk-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
28
|
+
.vk-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
|
|
29
|
+
.vk-table{width:100%;border-collapse:collapse;font-size:13px}
|
|
30
|
+
.vk-table th{background:rgba(0,200,255,.08);color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:10px;font-weight:700;letter-spacing:.06em;padding:10px 12px;text-align:left;border-bottom:1px solid rgba(0,200,255,.15)}
|
|
31
|
+
.vk-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
|
|
32
|
+
.vk-table tr:last-child td{border-bottom:none}
|
|
33
|
+
.vk-footer{display:flex;justify-content:flex-end}
|
|
34
|
+
.vk-btn{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:9px 22px;cursor:pointer}
|
|
35
|
+
`]
|
|
36
|
+
})
|
|
37
|
+
export class ViewKarmaComponent {
|
|
38
|
+
@Input() show = false;
|
|
39
|
+
@Output() close = new EventEmitter<void>();
|
|
40
|
+
karmaList = signal([
|
|
41
|
+
{ avatar: 'CosmicSage', karma: 1500, level: 20, rank: 1 },
|
|
42
|
+
{ avatar: 'StarWalker', karma: 840, level: 12, rank: 2 },
|
|
43
|
+
{ avatar: 'NebulaDancer', karma: 420, level: 7, rank: 3 },
|
|
44
|
+
{ avatar: 'VoidWatcher', karma: 210, level: 4, rank: 4 },
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'oasis-vote-karma',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule, FormsModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="vok-shell">
|
|
11
|
+
<div class="vok-header"><span>🗳️</span><h2 class="vok-title">Vote Karma</h2></div>
|
|
12
|
+
<div class="vok-field"><label class="vok-lbl">Avatar Name</label><input class="vok-input" [(ngModel)]="avatarName" placeholder="Enter avatar name..." /></div>
|
|
13
|
+
<div class="vok-vote-row">
|
|
14
|
+
<button class="vok-upvote" (click)="vote('up')">👍 Upvote Karma</button>
|
|
15
|
+
<button class="vok-downvote" (click)="vote('down')">👎 Downvote Karma</button>
|
|
16
|
+
</div>
|
|
17
|
+
@if (message()) { <div class="vok-message">{{ message() }}</div> }
|
|
18
|
+
<div class="vok-footer"><button class="vok-cancel" (click)="close.emit()">Close</button></div>
|
|
19
|
+
</div>
|
|
20
|
+
`,
|
|
21
|
+
styles: [`
|
|
22
|
+
.vok-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
|
|
23
|
+
.vok-header{display:flex;align-items:center;gap:10px}
|
|
24
|
+
.vok-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
25
|
+
.vok-field{display:flex;flex-direction:column;gap:6px}
|
|
26
|
+
.vok-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
|
|
27
|
+
.vok-input{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
|
|
28
|
+
.vok-input:focus{border-color:rgba(0,200,255,.6)}
|
|
29
|
+
.vok-vote-row{display:flex;gap:12px}
|
|
30
|
+
.vok-upvote,.vok-downvote{flex:1;border:none;border-radius:8px;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:12px;cursor:pointer;transition:opacity .2s}
|
|
31
|
+
.vok-upvote{background:linear-gradient(135deg,#00c864,#007840);color:#fff}
|
|
32
|
+
.vok-downvote{background:linear-gradient(135deg,#ff4444,#cc0000);color:#fff}
|
|
33
|
+
.vok-upvote:hover,.vok-downvote:hover{opacity:.85}
|
|
34
|
+
.vok-message{background:rgba(0,200,255,.08);border:1px solid rgba(0,200,255,.2);border-radius:8px;color:#c8dff0;font-size:13px;padding:12px 16px;text-align:center}
|
|
35
|
+
.vok-footer{display:flex;justify-content:flex-end}
|
|
36
|
+
.vok-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:8px 20px;cursor:pointer}
|
|
37
|
+
`]
|
|
38
|
+
})
|
|
39
|
+
export class VoteKarmaComponent {
|
|
40
|
+
@Input() show = false;
|
|
41
|
+
@Output() close = new EventEmitter<void>();
|
|
42
|
+
avatarName = '';
|
|
43
|
+
message = signal('');
|
|
44
|
+
vote(direction: 'up' | 'down') {
|
|
45
|
+
if (!this.avatarName.trim()) { this.message.set('Please enter an avatar name.'); return; }
|
|
46
|
+
this.message.set(direction === 'up' ? 'Upvoted karma for ' + this.avatarName + '!' : 'Downvoted karma for ' + this.avatarName + '.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Component, signal, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { OasisService } from '../../services/oasis.service';
|
|
4
|
+
|
|
5
|
+
type Tab = 'records' | 'stats' | 'weightings';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'oasis-karma-panel',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule],
|
|
11
|
+
template: `
|
|
12
|
+
<div class="kp-shell">
|
|
13
|
+
<div class="kp-banner">
|
|
14
|
+
<div class="kp-stat">
|
|
15
|
+
<div class="kp-stat-label">KARMA</div>
|
|
16
|
+
<div class="kp-stat-value">{{ oasis.session()?.karma ?? '—' }}</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="kp-stat">
|
|
19
|
+
<div class="kp-stat-label">LEVEL</div>
|
|
20
|
+
<div class="kp-stat-value kp-stat-value--level">{{ level() }}</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="kp-stat">
|
|
23
|
+
<div class="kp-stat-label">AVATAR TYPE</div>
|
|
24
|
+
<div class="kp-stat-value kp-stat-value--type">User</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="kp-tabs">
|
|
29
|
+
<button class="kp-tab" [class.kp-tab--active]="activeTab() === 'records'" (click)="activeTab.set('records')">Akashic Records</button>
|
|
30
|
+
<button class="kp-tab" [class.kp-tab--active]="activeTab() === 'stats'" (click)="activeTab.set('stats')">Stats</button>
|
|
31
|
+
<button class="kp-tab" [class.kp-tab--active]="activeTab() === 'weightings'" (click)="activeTab.set('weightings')">Weightings</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
@if (activeTab() === 'records') {
|
|
35
|
+
<div class="kp-panel kp-empty">
|
|
36
|
+
<div class="kp-empty-icon">📜</div>
|
|
37
|
+
<p>No karma records found.<br>Complete actions in the OASIS ecosystem to build your Akashic Record.</p>
|
|
38
|
+
</div>
|
|
39
|
+
}
|
|
40
|
+
@if (activeTab() === 'stats') {
|
|
41
|
+
<div class="kp-panel kp-empty">
|
|
42
|
+
<div class="kp-empty-icon">📊</div>
|
|
43
|
+
<p>No stats available yet.</p>
|
|
44
|
+
</div>
|
|
45
|
+
}
|
|
46
|
+
@if (activeTab() === 'weightings') {
|
|
47
|
+
<div class="kp-panel">
|
|
48
|
+
<div class="kp-vote-banner">
|
|
49
|
+
<div>
|
|
50
|
+
<strong>Karma Voting</strong>
|
|
51
|
+
<p>Community voting to adjust karma weightings is coming soon.</p>
|
|
52
|
+
</div>
|
|
53
|
+
<button class="kp-btn-ghost">Vote on Weightings <span class="kp-soon">Coming Soon</span></button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
58
|
+
`,
|
|
59
|
+
styles: [`
|
|
60
|
+
.kp-shell { color:#fff; display:flex; flex-direction:column; gap:20px; }
|
|
61
|
+
.kp-banner { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
62
|
+
.kp-stat { background:rgba(0,200,255,.06); border:1px solid rgba(0,200,255,.12); border-radius:10px; padding:14px; text-align:center; }
|
|
63
|
+
.kp-stat-label { font-size:10px; font-weight:700; letter-spacing:.1em; color:#7a9bbf; text-transform:uppercase; margin-bottom:6px; }
|
|
64
|
+
.kp-stat-value { font-family:'Orbitron',sans-serif; font-size:22px; color:#00c8ff; }
|
|
65
|
+
.kp-stat-value--level { color:#a78bfa; }
|
|
66
|
+
.kp-stat-value--type { color:#48dc82; font-size:16px; }
|
|
67
|
+
.kp-tabs { display:flex; gap:4px; border-bottom:1px solid rgba(0,200,255,.12); }
|
|
68
|
+
.kp-tab { background:none; border:none; color:#7a9bbf; font-size:13px; padding:10px 16px; cursor:pointer; border-bottom:2px solid transparent; transition:all .2s; }
|
|
69
|
+
.kp-tab--active { color:#00c8ff; border-bottom-color:#00c8ff; }
|
|
70
|
+
.kp-panel { padding:16px 0; }
|
|
71
|
+
.kp-empty { display:flex; flex-direction:column; align-items:center; gap:10px; padding:40px 16px; text-align:center; color:#7a9bbf; font-size:13px; line-height:1.6; }
|
|
72
|
+
.kp-empty-icon { font-size:32px; }
|
|
73
|
+
.kp-vote-banner { display:flex; align-items:center; justify-content:space-between; gap:16px; background:rgba(255,255,255,.03); border:1px solid rgba(0,200,255,.1); border-radius:10px; padding:16px; font-size:13px; color:#a8bfd8; line-height:1.5; }
|
|
74
|
+
.kp-vote-banner strong { display:block; color:#fff; margin-bottom:4px; }
|
|
75
|
+
.kp-btn-ghost { flex-shrink:0; background:transparent; border:1px solid rgba(0,200,255,.3); color:#00c8ff; border-radius:6px; padding:8px 14px; font-size:12px; cursor:pointer; white-space:nowrap; }
|
|
76
|
+
.kp-soon { display:inline-block; background:rgba(255,180,60,.15); color:#ffb43c; border-radius:999px; font-size:9px; padding:2px 6px; margin-left:6px; letter-spacing:.06em; vertical-align:middle; }
|
|
77
|
+
`]
|
|
78
|
+
})
|
|
79
|
+
export class KarmaPanelComponent {
|
|
80
|
+
oasis = inject(OasisService);
|
|
81
|
+
activeTab = signal<Tab>('records');
|
|
82
|
+
|
|
83
|
+
level() {
|
|
84
|
+
const k = this.oasis.session()?.karma ?? 0;
|
|
85
|
+
return Math.floor(Math.sqrt(k / 100)) + 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { OasisService } from '../../services/oasis.service';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'oasis-karma-toast',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule],
|
|
9
|
+
template: `
|
|
10
|
+
@if (toast()) {
|
|
11
|
+
<div class="karma-toast">
|
|
12
|
+
<span class="kt-amount">+{{ toast()!.amount }} ✦</span>
|
|
13
|
+
<span class="kt-msg">{{ toast()!.message }}</span>
|
|
14
|
+
</div>
|
|
15
|
+
}
|
|
16
|
+
`,
|
|
17
|
+
styles: [`
|
|
18
|
+
.karma-toast {
|
|
19
|
+
position: fixed; bottom: 32px; right: 32px; z-index: 9999;
|
|
20
|
+
background: linear-gradient(135deg, rgba(232,121,249,.15), rgba(168,85,247,.1));
|
|
21
|
+
border: 1px solid rgba(232,121,249,.4); border-radius: 14px;
|
|
22
|
+
padding: 16px 24px; display: flex; flex-direction: column; gap: 4px;
|
|
23
|
+
backdrop-filter: blur(12px);
|
|
24
|
+
animation: slideIn .35s ease;
|
|
25
|
+
}
|
|
26
|
+
.kt-amount { font-family: 'Orbitron', sans-serif; font-size: 22px; font-weight: 900; color: #e879f9; }
|
|
27
|
+
.kt-msg { font-family: 'Share Tech Mono', monospace; font-size: 11px; letter-spacing: .1em; color: #a8bfd8; }
|
|
28
|
+
@keyframes slideIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
29
|
+
`]
|
|
30
|
+
})
|
|
31
|
+
export class KarmaToastComponent {
|
|
32
|
+
toast = inject(OasisService).karmaToast;
|
|
33
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Component, signal, Output, EventEmitter, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { OasisService } from '../../services/oasis.service';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'oasis-login',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [CommonModule, FormsModule],
|
|
10
|
+
template: `
|
|
11
|
+
<div class="oasis-auth-form">
|
|
12
|
+
<div class="oasis-auth-header">
|
|
13
|
+
<h2 class="oasis-auth-title">Beam In</h2>
|
|
14
|
+
<p class="oasis-auth-sub">Don't have an account? <a class="oasis-link" (click)="switchTo.emit('signup')" href="javascript:void(0)">Sign Up</a></p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
@if (error()) {
|
|
18
|
+
<div class="oasis-auth-error">{{ error() }}</div>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
<div class="oasis-field">
|
|
22
|
+
<label class="oasis-label" for="oasis-login-username">Username</label>
|
|
23
|
+
<input class="oasis-input" id="oasis-login-username" type="text" [(ngModel)]="username" placeholder="yourusername" autocomplete="username" required />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="oasis-field">
|
|
27
|
+
<label class="oasis-label" for="oasis-login-password">Password</label>
|
|
28
|
+
<div class="oasis-input-wrap">
|
|
29
|
+
<input class="oasis-input" id="oasis-login-password" [type]="showPwd ? 'text' : 'password'" [(ngModel)]="password" placeholder="••••••••" autocomplete="current-password" required />
|
|
30
|
+
<button class="oasis-pwd-toggle" type="button" (click)="showPwd = !showPwd" [attr.aria-label]="showPwd ? 'Hide password' : 'Show password'">
|
|
31
|
+
{{ showPwd ? '🙈' : '👁️' }}
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
<a class="oasis-link oasis-link--small" (click)="switchTo.emit('forgot')" href="javascript:void(0)">Forgot Password?</a>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="oasis-field oasis-field--row">
|
|
38
|
+
<input class="oasis-checkbox" id="oasis-remember" type="checkbox" [(ngModel)]="rememberMe" />
|
|
39
|
+
<label class="oasis-label oasis-label--inline" for="oasis-remember">Remember Me</label>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<button class="oasis-btn" type="button" [disabled]="loading()" (click)="submit()">
|
|
43
|
+
{{ loading() ? 'Connecting…' : 'Beam In' }}
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
`,
|
|
47
|
+
styles: [`
|
|
48
|
+
.oasis-auth-form { display:flex; flex-direction:column; gap:20px; }
|
|
49
|
+
.oasis-auth-header { text-align:center; }
|
|
50
|
+
.oasis-auth-title { font-family:'Orbitron',sans-serif; font-size:22px; color:#fff; margin:0 0 6px; }
|
|
51
|
+
.oasis-auth-sub { font-size:13px; color:#7a9bbf; margin:0; }
|
|
52
|
+
.oasis-auth-error { background:rgba(255,80,80,.12); border:1px solid rgba(255,80,80,.3); color:#ff6b6b; border-radius:8px; padding:10px 14px; font-size:13px; }
|
|
53
|
+
.oasis-field { display:flex; flex-direction:column; gap:6px; }
|
|
54
|
+
.oasis-field--row { flex-direction:row; align-items:center; gap:10px; }
|
|
55
|
+
.oasis-label { font-size:12px; font-weight:600; letter-spacing:.06em; color:#7a9bbf; text-transform:uppercase; }
|
|
56
|
+
.oasis-label--inline { text-transform:none; font-size:13px; color:#a8bfd8; }
|
|
57
|
+
.oasis-input-wrap { position:relative; }
|
|
58
|
+
.oasis-input { width:100%; background:rgba(255,255,255,.05); border:1px solid rgba(0,200,255,.2); border-radius:8px; padding:10px 14px; color:#fff; font-size:14px; outline:none; box-sizing:border-box; transition:border-color .2s; }
|
|
59
|
+
.oasis-input:focus { border-color:rgba(0,200,255,.5); }
|
|
60
|
+
.oasis-input-wrap .oasis-input { padding-right:44px; }
|
|
61
|
+
.oasis-pwd-toggle { position:absolute; right:10px; top:50%; transform:translateY(-50%); background:none; border:none; cursor:pointer; font-size:16px; padding:0; line-height:1; }
|
|
62
|
+
.oasis-checkbox { width:16px; height:16px; accent-color:#00c8ff; cursor:pointer; flex-shrink:0; }
|
|
63
|
+
.oasis-link { color:#00c8ff; text-decoration:none; cursor:pointer; }
|
|
64
|
+
.oasis-link--small { font-size:12px; margin-top:2px; }
|
|
65
|
+
.oasis-btn { background:linear-gradient(135deg,#00c8ff,#0080ff); border:none; border-radius:8px; color:#fff; font-family:'Orbitron',sans-serif; font-size:13px; font-weight:700; letter-spacing:.08em; padding:12px; cursor:pointer; transition:opacity .2s; }
|
|
66
|
+
.oasis-btn:disabled { opacity:.5; cursor:not-allowed; }
|
|
67
|
+
`]
|
|
68
|
+
})
|
|
69
|
+
export class LoginComponent {
|
|
70
|
+
@Output() switchTo = new EventEmitter<'signup' | 'forgot'>();
|
|
71
|
+
@Output() loggedIn = new EventEmitter<void>();
|
|
72
|
+
|
|
73
|
+
private oasis = inject(OasisService);
|
|
74
|
+
|
|
75
|
+
username = '';
|
|
76
|
+
password = '';
|
|
77
|
+
rememberMe = false;
|
|
78
|
+
showPwd = false;
|
|
79
|
+
loading = signal(false);
|
|
80
|
+
error = signal('');
|
|
81
|
+
|
|
82
|
+
async submit() {
|
|
83
|
+
if (!this.username || !this.password) { this.error.set('Please fill in all fields.'); return; }
|
|
84
|
+
this.loading.set(true);
|
|
85
|
+
this.error.set('');
|
|
86
|
+
try {
|
|
87
|
+
await this.oasis.login(this.username, this.password);
|
|
88
|
+
this.loggedIn.emit();
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
this.error.set(e?.message ?? 'Login failed. Please check your credentials.');
|
|
91
|
+
} finally {
|
|
92
|
+
this.loading.set(false);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-add-2d-object-map',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="mf-shell">
|
|
10
|
+
<div class="mf-header"><span>🗺️</span><h2 class="mf-title">Add 2D Object to Map</h2></div>
|
|
11
|
+
<div class="mf-body">
|
|
12
|
+
<div class="mf-field"><label class="mf-lbl">Object Name</label><input class="mf-inp" placeholder="Object Name..." /></div>
|
|
13
|
+
<div class="mf-field"><label class="mf-lbl">X Position</label><input class="mf-inp" placeholder="X Position..." /></div>
|
|
14
|
+
<div class="mf-field"><label class="mf-lbl">Y Position</label><input class="mf-inp" placeholder="Y Position..." /></div>
|
|
15
|
+
<div class="mf-field"><label class="mf-lbl">Layer</label><input class="mf-inp" placeholder="Layer..." /></div>
|
|
16
|
+
<div class="mf-field"><label class="mf-lbl">Description</label><input class="mf-inp" placeholder="Description..." /></div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="mf-footer">
|
|
19
|
+
<button class="mf-save" (click)="save()">Save</button>
|
|
20
|
+
<button class="mf-cancel" (click)="close.emit()">Cancel</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
styles: [`
|
|
25
|
+
.mf-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
|
|
26
|
+
.mf-header{display:flex;align-items:center;gap:10px}
|
|
27
|
+
.mf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
28
|
+
.mf-body{display:flex;flex-direction:column;gap:12px}
|
|
29
|
+
.mf-field{display:flex;flex-direction:column;gap:5px}
|
|
30
|
+
.mf-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
|
|
31
|
+
.mf-inp{background:rgba(255,255,255,.05);border:1px solid rgba(0,200,255,.25);border-radius:8px;color:#fff;font-size:13px;padding:10px 14px;outline:none}
|
|
32
|
+
.mf-inp:focus{border-color:rgba(0,200,255,.6)}
|
|
33
|
+
.mf-footer{display:flex;gap:10px;justify-content:flex-end}
|
|
34
|
+
.mf-save{background:linear-gradient(135deg,#00c8ff,#0080ff);border:none;border-radius:8px;color:#fff;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;padding:10px 22px;cursor:pointer}
|
|
35
|
+
.mf-cancel{background:transparent;border:1px solid rgba(0,200,255,.3);border-radius:8px;color:#00c8ff;font-family:'Orbitron',sans-serif;font-size:11px;font-weight:700;padding:10px 18px;cursor:pointer}
|
|
36
|
+
`]
|
|
37
|
+
})
|
|
38
|
+
export class Add2dObjectMapComponent {
|
|
39
|
+
@Input() show = false;
|
|
40
|
+
@Output() close = new EventEmitter<void>();
|
|
41
|
+
save() { this.close.emit(); }
|
|
42
|
+
}
|