@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
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oasisomniverse/angular",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OASIS Omniverse Angular 17 standalone UI components",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"oasis",
|
|
7
|
+
"omniverse",
|
|
8
|
+
"angular",
|
|
9
|
+
"components",
|
|
10
|
+
"standalone"
|
|
11
|
+
],
|
|
12
|
+
"author": "NextGenSoftware",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/NextGenSoftwareUK/OASIS-Angular-UI-Component-Library.git"
|
|
17
|
+
},
|
|
18
|
+
"main": "src/index.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.ts",
|
|
21
|
+
"./components/*": "./src/components/*.component.ts"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@angular/core": "\u003e=17.0.0",
|
|
28
|
+
"@angular/common": "\u003e=17.0.0",
|
|
29
|
+
"@oasisomniverse/web4-api": "\u003e=2.0.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -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-avatar-wallet',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="aw-shell">
|
|
10
|
+
<div class="aw-header"><span class="aw-icon">💰</span><h2 class="aw-title">Avatar Wallet</h2></div>
|
|
11
|
+
<div class="aw-table-wrap">
|
|
12
|
+
<table class="aw-table">
|
|
13
|
+
<thead><tr><th>Date</th><th>Note</th><th>From Avatar</th><th>Token</th><th>Amount</th><th>Balance</th><th>Provider</th><th>Type</th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
@for (row of transactions(); track row.id) {
|
|
16
|
+
<tr><td>{{ row.date }}</td><td>{{ row.note }}</td><td>{{ row.fromAvatar }}</td><td>{{ row.token }}</td><td>{{ row.amount }}</td><td>{{ row.balance }}</td><td>{{ row.provider }}</td><td><span class="aw-badge aw-badge--{{ row.type.toLowerCase() }}">{{ row.type }}</span></td></tr>
|
|
17
|
+
}
|
|
18
|
+
</tbody>
|
|
19
|
+
</table>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="aw-footer"><button class="aw-btn" (click)="close.emit()">Close</button></div>
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
styles: [`
|
|
25
|
+
.aw-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
|
|
26
|
+
.aw-header{display:flex;align-items:center;gap:10px}
|
|
27
|
+
.aw-icon{font-size:26px}
|
|
28
|
+
.aw-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
29
|
+
.aw-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
|
|
30
|
+
.aw-table{width:100%;border-collapse:collapse;font-size:13px}
|
|
31
|
+
.aw-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);white-space:nowrap}
|
|
32
|
+
.aw-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
|
|
33
|
+
.aw-table tr:last-child td{border-bottom:none}
|
|
34
|
+
.aw-table tr:hover td{background:rgba(0,200,255,.04)}
|
|
35
|
+
.aw-badge{border-radius:999px;padding:2px 9px;font-size:10px;font-weight:700}
|
|
36
|
+
.aw-badge--credit{background:rgba(0,200,100,.15);color:#00c864;border:1px solid rgba(0,200,100,.3)}
|
|
37
|
+
.aw-badge--debit{background:rgba(255,80,80,.12);color:#ff5050;border:1px solid rgba(255,80,80,.3)}
|
|
38
|
+
.aw-footer{display:flex;justify-content:flex-end}
|
|
39
|
+
.aw-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}
|
|
40
|
+
`]
|
|
41
|
+
})
|
|
42
|
+
export class AvatarWalletComponent {
|
|
43
|
+
@Input() show = false;
|
|
44
|
+
@Output() close = new EventEmitter<void>();
|
|
45
|
+
transactions = signal([
|
|
46
|
+
{ id: 1, date: '2026-07-01', note: 'Quest reward', fromAvatar: 'StarWalker', token: 'SEEDS', amount: '50', balance: '350', provider: 'Holochain', type: 'Credit' },
|
|
47
|
+
{ id: 2, date: '2026-07-03', note: 'NFT purchase', fromAvatar: 'Self', token: 'ETH', amount: '0.05', balance: '1.12', provider: 'Ethereum', type: 'Debit' },
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-edit-avatar',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="popup-shell">
|
|
10
|
+
<div class="popup-icon">✏️</div>
|
|
11
|
+
<h2 class="popup-title">Edit Avatar</h2>
|
|
12
|
+
<p class="popup-body">UI Coming Soon — access this feature via the OASIS API from the Developer menu.</p>
|
|
13
|
+
<button class="popup-btn" (click)="close.emit()">OK</button>
|
|
14
|
+
</div>
|
|
15
|
+
`,
|
|
16
|
+
styles: [`
|
|
17
|
+
.popup-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;text-align:center}
|
|
18
|
+
.popup-icon{font-size:48px}
|
|
19
|
+
.popup-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
20
|
+
.popup-body{font-size:13px;color:#a8bfd8;line-height:1.6;margin:0;max-width:380px}
|
|
21
|
+
.popup-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 28px;cursor:pointer;transition:opacity .2s}
|
|
22
|
+
.popup-btn:hover{opacity:.85}
|
|
23
|
+
`]
|
|
24
|
+
})
|
|
25
|
+
export class EditAvatarComponent {
|
|
26
|
+
@Input() show = false;
|
|
27
|
+
@Output() close = new EventEmitter<void>();
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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-avatar',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule, FormsModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="sa-shell">
|
|
11
|
+
<div class="sa-header"><span>🔍</span><h2 class="sa-title">Search Avatars</h2></div>
|
|
12
|
+
<div class="sa-row">
|
|
13
|
+
<input class="sa-input" [(ngModel)]="query" placeholder="Username or avatar name..." (keyup.enter)="search()" />
|
|
14
|
+
<button class="sa-btn" (click)="search()">Search</button>
|
|
15
|
+
</div>
|
|
16
|
+
@for (r of results(); track r.name) {
|
|
17
|
+
<div class="sa-card"><div class="sa-name">{{ r.name }}</div><div class="sa-meta">Level {{ r.level }} · {{ r.karma }} karma</div></div>
|
|
18
|
+
}
|
|
19
|
+
<button class="sa-cancel" (click)="close.emit()">Close</button>
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
styles: [`
|
|
23
|
+
.sa-shell{display:flex;flex-direction:column;gap:14px;padding:20px}
|
|
24
|
+
.sa-header{display:flex;align-items:center;gap:10px}
|
|
25
|
+
.sa-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
26
|
+
.sa-row{display:flex;gap:10px}
|
|
27
|
+
.sa-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}
|
|
28
|
+
.sa-input:focus{border-color:rgba(0,200,255,.6)}
|
|
29
|
+
.sa-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}
|
|
30
|
+
.sa-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,200,255,.12);border-radius:8px;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
|
|
31
|
+
.sa-name{font-family:'Orbitron',sans-serif;font-size:13px;color:#fff}
|
|
32
|
+
.sa-meta{font-size:12px;color:#7a9bbf}
|
|
33
|
+
.sa-cancel{align-self:flex-end;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}
|
|
34
|
+
`]
|
|
35
|
+
})
|
|
36
|
+
export class SearchAvatarComponent {
|
|
37
|
+
@Input() show = false;
|
|
38
|
+
@Output() close = new EventEmitter<void>();
|
|
39
|
+
query = '';
|
|
40
|
+
results = signal<{name:string;level:number;karma:number}[]>([]);
|
|
41
|
+
search() {
|
|
42
|
+
if (!this.query.trim()) return;
|
|
43
|
+
this.results.set([{ name: this.query, level: 7, karma: 420 }]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -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-avatar',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="va-shell">
|
|
10
|
+
<div class="va-header"><span>👤</span><h2 class="va-title">View Avatars</h2></div>
|
|
11
|
+
<div class="va-table-wrap">
|
|
12
|
+
<table class="va-table">
|
|
13
|
+
<thead><tr><th>Avatar</th><th>Level</th><th>Karma</th><th>Sex</th><th>Created</th><th>Online</th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
@for (a of avatars(); track a.name) {
|
|
16
|
+
<tr>
|
|
17
|
+
<td>{{ a.name }}</td><td>{{ a.level }}</td><td>{{ a.karma }}</td><td>{{ a.sex }}</td><td>{{ a.created }}</td>
|
|
18
|
+
<td><span class="va-dot" [class.va-dot--on]="a.online">{{ a.online ? '🟢' : '⚪' }}</span></td>
|
|
19
|
+
</tr>
|
|
20
|
+
}
|
|
21
|
+
</tbody>
|
|
22
|
+
</table>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="va-footer"><button class="va-btn" (click)="close.emit()">Close</button></div>
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
styles: [`
|
|
28
|
+
.va-shell{display:flex;flex-direction:column;gap:16px;padding:20px}
|
|
29
|
+
.va-header{display:flex;align-items:center;gap:10px}
|
|
30
|
+
.va-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
31
|
+
.va-table-wrap{overflow-x:auto;border:1px solid rgba(0,200,255,.15);border-radius:10px}
|
|
32
|
+
.va-table{width:100%;border-collapse:collapse;font-size:13px}
|
|
33
|
+
.va-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)}
|
|
34
|
+
.va-table td{color:#c8dff0;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.04)}
|
|
35
|
+
.va-table tr:last-child td{border-bottom:none}
|
|
36
|
+
.va-table tr:hover td{background:rgba(0,200,255,.04)}
|
|
37
|
+
.va-footer{display:flex;justify-content:flex-end}
|
|
38
|
+
.va-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 ViewAvatarComponent {
|
|
42
|
+
@Input() show = false;
|
|
43
|
+
@Output() close = new EventEmitter<void>();
|
|
44
|
+
avatars = signal([
|
|
45
|
+
{ name: 'StarWalker', level: 12, karma: 840, sex: 'Male', created: '2024-01-15', online: true },
|
|
46
|
+
{ name: 'NebulaDancer', level: 7, karma: 420, sex: 'Female', created: '2024-03-22', online: false },
|
|
47
|
+
{ name: 'CosmicSage', level: 20, karma: 1500, sex: 'Male', created: '2023-11-01', online: true },
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Component, signal, 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-avatar-connect',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [CommonModule, FormsModule],
|
|
10
|
+
template: `
|
|
11
|
+
@if (session()) {
|
|
12
|
+
<div class="avatar-badge">
|
|
13
|
+
<span class="avatar-icon">⬡</span>
|
|
14
|
+
<span class="avatar-name">{{ session()!.username }}</span>
|
|
15
|
+
<span class="karma-pill">{{ session()!.karma | number }} ✦</span>
|
|
16
|
+
<button class="logout-btn" (click)="oasis.logout()">✕</button>
|
|
17
|
+
</div>
|
|
18
|
+
} @else {
|
|
19
|
+
<button class="connect-btn" (click)="open = true">⬡ CONNECT AVATAR</button>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@if (open) {
|
|
23
|
+
<div class="modal-backdrop" (click)="open = false">
|
|
24
|
+
<div class="modal-box" (click)="$event.stopPropagation()">
|
|
25
|
+
<button class="modal-close" (click)="open = false">✕</button>
|
|
26
|
+
<div class="modal-icon">⬡</div>
|
|
27
|
+
<h2 class="modal-title">Connect Your Avatar</h2>
|
|
28
|
+
<p class="modal-sub">Sign in to your OASIS avatar to earn karma and participate in Global Healing Network.</p>
|
|
29
|
+
<input [(ngModel)]="username" placeholder="Avatar username" class="oasis-input" />
|
|
30
|
+
<input [(ngModel)]="password" type="password" placeholder="Password" class="oasis-input" />
|
|
31
|
+
@if (error) { <p class="error-msg">{{ error }}</p> }
|
|
32
|
+
<button class="btn-primary" [disabled]="loading" (click)="login()">
|
|
33
|
+
{{ loading ? 'Connecting…' : 'Connect Avatar' }}
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
}
|
|
38
|
+
`,
|
|
39
|
+
styles: [`
|
|
40
|
+
.connect-btn { background: none; border: 1px solid rgba(232,121,249,.4); border-radius: 999px; padding: 8px 18px; color: #e879f9; font-family: 'Share Tech Mono', monospace; font-size: 11px; letter-spacing: .1em; cursor: pointer; transition: background .2s; }
|
|
41
|
+
.connect-btn:hover { background: rgba(232,121,249,.08); }
|
|
42
|
+
.avatar-badge { display: flex; align-items: center; gap: 8px; }
|
|
43
|
+
.avatar-icon { color: #e879f9; font-size: 16px; }
|
|
44
|
+
.avatar-name { font-family: 'Share Tech Mono', monospace; font-size: 12px; color: #fff; letter-spacing: .06em; }
|
|
45
|
+
.karma-pill { background: rgba(232,121,249,.12); border: 1px solid rgba(232,121,249,.3); border-radius: 999px; padding: 2px 10px; font-family: 'Share Tech Mono', monospace; font-size: 11px; color: #e879f9; }
|
|
46
|
+
.logout-btn { background: none; border: none; color: #a8bfd8; cursor: pointer; font-size: 14px; margin-left: 4px; }
|
|
47
|
+
.modal-backdrop { position: fixed; inset: 0; z-index: 9000; background: rgba(3,7,20,.85); backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; padding: 20px; }
|
|
48
|
+
.modal-box { background: #0a1535; border: 1px solid rgba(232,121,249,.25); border-radius: 20px; padding: 40px 36px; max-width: 440px; width: 100%; position: relative; }
|
|
49
|
+
.modal-close { position: absolute; top: 16px; right: 18px; background: none; border: none; color: #a8bfd8; font-size: 20px; cursor: pointer; }
|
|
50
|
+
.modal-icon { font-size: 40px; text-align: center; color: #e879f9; margin-bottom: 16px; }
|
|
51
|
+
.modal-title { font-family: 'Orbitron', sans-serif; font-size: 20px; font-weight: 700; color: #fff; text-align: center; margin: 0 0 8px; }
|
|
52
|
+
.modal-sub { font-size: 14px; color: #a8bfd8; text-align: center; margin: 0 0 24px; line-height: 1.6; }
|
|
53
|
+
.oasis-input { width: 100%; background: rgba(255,255,255,.05); border: 1px solid rgba(232,121,249,.2); border-radius: 8px; padding: 11px 14px; color: #fff; font-size: 14px; outline: none; box-sizing: border-box; margin-bottom: 12px; display: block; }
|
|
54
|
+
.btn-primary { width: 100%; padding: 14px; background: linear-gradient(135deg, #e879f9, #a855f7); border: none; border-radius: 10px; color: #fff; font-family: 'Orbitron', sans-serif; font-size: 13px; font-weight: 700; letter-spacing: .08em; cursor: pointer; margin-top: 4px; }
|
|
55
|
+
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
|
|
56
|
+
.error-msg { color: #f87171; font-size: 13px; text-align: center; margin-bottom: 8px; }
|
|
57
|
+
`]
|
|
58
|
+
})
|
|
59
|
+
export class AvatarConnectComponent {
|
|
60
|
+
oasis = inject(OasisService);
|
|
61
|
+
session = this.oasis.session;
|
|
62
|
+
open = false;
|
|
63
|
+
loading = false;
|
|
64
|
+
error = '';
|
|
65
|
+
username = '';
|
|
66
|
+
password = '';
|
|
67
|
+
|
|
68
|
+
async login(): Promise<void> {
|
|
69
|
+
if (!this.username || !this.password) { this.error = 'Please fill in both fields.'; return; }
|
|
70
|
+
this.loading = true; this.error = '';
|
|
71
|
+
try {
|
|
72
|
+
await this.oasis.login(this.username, this.password);
|
|
73
|
+
this.open = false; this.username = ''; this.password = '';
|
|
74
|
+
} catch {
|
|
75
|
+
this.error = 'Login failed. Please check your credentials.';
|
|
76
|
+
} finally { this.loading = false; }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Component, OnInit, signal, 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
|
+
interface AvatarFields {
|
|
7
|
+
title: string; firstName: string; lastName: string;
|
|
8
|
+
username: string; email: string; address: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'oasis-avatar-profile',
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [CommonModule, FormsModule],
|
|
15
|
+
template: `
|
|
16
|
+
<div class="avp-shell">
|
|
17
|
+
@if (!oasis.session()) {
|
|
18
|
+
<div class="avp-empty">Please <strong>Beam In</strong> to view your avatar.</div>
|
|
19
|
+
} @else {
|
|
20
|
+
<div class="avp-layout">
|
|
21
|
+
<aside class="avp-summary">
|
|
22
|
+
<div class="avp-avatar-img">
|
|
23
|
+
<span class="avp-avatar-icon">👤</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="avp-name">{{ oasis.session()?.username }}</div>
|
|
26
|
+
<div class="avp-stats">
|
|
27
|
+
<div class="avp-stat">
|
|
28
|
+
<span class="avp-stat-icon">⚡</span>
|
|
29
|
+
<div>
|
|
30
|
+
<div class="avp-stat-value">{{ oasis.session()?.karma ?? '—' }}</div>
|
|
31
|
+
<div class="avp-stat-label">Karma</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</aside>
|
|
36
|
+
|
|
37
|
+
<div class="avp-fields">
|
|
38
|
+
<div class="avp-section-label">PROFILE</div>
|
|
39
|
+
<div class="avp-field-row">
|
|
40
|
+
<label class="avp-label" for="avp-title">Title</label>
|
|
41
|
+
<input class="avp-input" id="avp-title" type="text" [(ngModel)]="fields.title" placeholder="Dr, Mr, Ms…" (input)="dirty = true" />
|
|
42
|
+
</div>
|
|
43
|
+
<div class="avp-field-row">
|
|
44
|
+
<label class="avp-label" for="avp-first">First Name</label>
|
|
45
|
+
<input class="avp-input" id="avp-first" type="text" [(ngModel)]="fields.firstName" placeholder="First name" (input)="dirty = true" />
|
|
46
|
+
</div>
|
|
47
|
+
<div class="avp-field-row">
|
|
48
|
+
<label class="avp-label" for="avp-last">Last Name</label>
|
|
49
|
+
<input class="avp-input" id="avp-last" type="text" [(ngModel)]="fields.lastName" placeholder="Last name" (input)="dirty = true" />
|
|
50
|
+
</div>
|
|
51
|
+
<div class="avp-field-row">
|
|
52
|
+
<label class="avp-label" for="avp-username">Username</label>
|
|
53
|
+
<input class="avp-input" id="avp-username" type="text" [(ngModel)]="fields.username" placeholder="Username" (input)="dirty = true" />
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="avp-section-label" style="margin-top:18px">CONTACT</div>
|
|
57
|
+
<div class="avp-field-row">
|
|
58
|
+
<label class="avp-label" for="avp-email">Email</label>
|
|
59
|
+
<input class="avp-input" id="avp-email" type="email" [(ngModel)]="fields.email" placeholder="name@example.com" (input)="dirty = true" />
|
|
60
|
+
</div>
|
|
61
|
+
<div class="avp-field-row">
|
|
62
|
+
<label class="avp-label" for="avp-address">Address</label>
|
|
63
|
+
<input class="avp-input" id="avp-address" type="text" [(ngModel)]="fields.address" placeholder="Optional" (input)="dirty = true" />
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
@if (status()) {
|
|
67
|
+
<div class="avp-status" [class.avp-status--error]="statusType() === 'error'">{{ status() }}</div>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@if (dirty) {
|
|
71
|
+
<div class="avp-actions">
|
|
72
|
+
<button class="avp-btn" (click)="save()" [disabled]="saving()">{{ saving() ? 'Saving…' : 'Save Changes' }}</button>
|
|
73
|
+
<button class="avp-btn avp-btn--ghost" (click)="discard()">Discard</button>
|
|
74
|
+
</div>
|
|
75
|
+
}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
}
|
|
79
|
+
</div>
|
|
80
|
+
`,
|
|
81
|
+
styles: [`
|
|
82
|
+
.avp-shell { color:#fff; }
|
|
83
|
+
.avp-empty { text-align:center; padding:40px; color:#7a9bbf; font-size:14px; }
|
|
84
|
+
.avp-layout { display:grid; grid-template-columns:180px 1fr; gap:28px; }
|
|
85
|
+
.avp-summary { display:flex; flex-direction:column; align-items:center; gap:14px; }
|
|
86
|
+
.avp-avatar-img { width:88px; height:88px; border-radius:50%; background:rgba(0,200,255,.1); border:2px solid rgba(0,200,255,.3); display:flex; align-items:center; justify-content:center; }
|
|
87
|
+
.avp-avatar-icon { font-size:40px; }
|
|
88
|
+
.avp-name { font-family:'Orbitron',sans-serif; font-size:13px; color:#fff; text-align:center; word-break:break-all; }
|
|
89
|
+
.avp-stats { display:flex; flex-direction:column; gap:8px; width:100%; }
|
|
90
|
+
.avp-stat { display:flex; align-items:center; gap:8px; background:rgba(0,200,255,.06); border-radius:8px; padding:8px 10px; }
|
|
91
|
+
.avp-stat-icon { font-size:16px; }
|
|
92
|
+
.avp-stat-value { font-family:'Orbitron',sans-serif; font-size:14px; color:#00c8ff; }
|
|
93
|
+
.avp-stat-label { font-size:11px; color:#7a9bbf; text-transform:uppercase; letter-spacing:.06em; }
|
|
94
|
+
.avp-fields { display:flex; flex-direction:column; gap:12px; }
|
|
95
|
+
.avp-section-label { font-size:10px; font-weight:700; letter-spacing:.12em; color:#7a9bbf; text-transform:uppercase; margin-bottom:4px; }
|
|
96
|
+
.avp-field-row { display:grid; grid-template-columns:90px 1fr; align-items:center; gap:12px; }
|
|
97
|
+
.avp-label { font-size:12px; color:#7a9bbf; text-align:right; }
|
|
98
|
+
.avp-input { width:100%; background:rgba(255,255,255,.05); border:1px solid rgba(0,200,255,.2); border-radius:6px; padding:8px 12px; color:#fff; font-size:13px; outline:none; box-sizing:border-box; transition:border-color .2s; }
|
|
99
|
+
.avp-input:focus { border-color:rgba(0,200,255,.5); }
|
|
100
|
+
.avp-status { margin-top:4px; font-size:13px; color:#48dc82; }
|
|
101
|
+
.avp-status--error { color:#ff6b6b; }
|
|
102
|
+
.avp-actions { display:flex; gap:10px; margin-top:4px; }
|
|
103
|
+
.avp-btn { background:linear-gradient(135deg,#00c8ff,#0080ff); border:none; border-radius:6px; color:#fff; font-family:'Orbitron',sans-serif; font-size:11px; font-weight:700; letter-spacing:.08em; padding:9px 16px; cursor:pointer; transition:opacity .2s; }
|
|
104
|
+
.avp-btn:disabled { opacity:.5; cursor:not-allowed; }
|
|
105
|
+
.avp-btn--ghost { background:transparent; border:1px solid rgba(0,200,255,.3); color:#00c8ff; }
|
|
106
|
+
`]
|
|
107
|
+
})
|
|
108
|
+
export class AvatarProfileComponent implements OnInit {
|
|
109
|
+
oasis = inject(OasisService);
|
|
110
|
+
|
|
111
|
+
fields: AvatarFields = { title: '', firstName: '', lastName: '', username: '', email: '', address: '' };
|
|
112
|
+
dirty = false;
|
|
113
|
+
saving = signal(false);
|
|
114
|
+
status = signal('');
|
|
115
|
+
statusType = signal<'ok' | 'error'>('ok');
|
|
116
|
+
|
|
117
|
+
ngOnInit() {
|
|
118
|
+
const sess = this.oasis.session();
|
|
119
|
+
if (sess) this.fields.username = sess.username;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async save() {
|
|
123
|
+
this.saving.set(true);
|
|
124
|
+
this.status.set('');
|
|
125
|
+
try {
|
|
126
|
+
// Avatar update would use oasis.avatar.updateAvatar() when available
|
|
127
|
+
await new Promise(r => setTimeout(r, 600));
|
|
128
|
+
this.dirty = false;
|
|
129
|
+
this.statusType.set('ok');
|
|
130
|
+
this.status.set('Changes saved.');
|
|
131
|
+
setTimeout(() => this.status.set(''), 3000);
|
|
132
|
+
} catch (e: any) {
|
|
133
|
+
this.statusType.set('error');
|
|
134
|
+
this.status.set(e?.message ?? 'Save failed.');
|
|
135
|
+
} finally {
|
|
136
|
+
this.saving.set(false);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
discard() {
|
|
141
|
+
const sess = this.oasis.session();
|
|
142
|
+
this.fields = { title: '', firstName: '', lastName: '', username: sess?.username ?? '', email: '', address: '' };
|
|
143
|
+
this.dirty = false;
|
|
144
|
+
this.status.set('');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-coming-soon',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="cs-shell">
|
|
10
|
+
<div class="cs-icon">🚀</div>
|
|
11
|
+
<h2 class="cs-title">{{ title }}</h2>
|
|
12
|
+
<p class="cs-body">{{ message }}</p>
|
|
13
|
+
<div class="cs-badge">Coming Soon</div>
|
|
14
|
+
</div>
|
|
15
|
+
`,
|
|
16
|
+
styles: [`
|
|
17
|
+
.cs-shell{display:flex;flex-direction:column;align-items:center;gap:16px;padding:40px 20px;text-align:center}
|
|
18
|
+
.cs-icon{font-size:48px}
|
|
19
|
+
.cs-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0}
|
|
20
|
+
.cs-body{font-size:14px;color:#7a9bbf;line-height:1.6;margin:0;max-width:360px}
|
|
21
|
+
.cs-badge{background:rgba(255,180,60,.12);border:1px solid rgba(255,180,60,.28);color:#ffb43c;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.1em;padding:5px 14px;text-transform:uppercase}
|
|
22
|
+
`]
|
|
23
|
+
})
|
|
24
|
+
export class ComingSoonComponent {
|
|
25
|
+
@Input() title = 'Feature Coming Soon';
|
|
26
|
+
@Input() message = 'This feature is currently under development and will be available soon.';
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'oasis-confirmation',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule],
|
|
8
|
+
template: `
|
|
9
|
+
<div class="conf-shell">
|
|
10
|
+
<div class="conf-icon">{{ icon }}</div>
|
|
11
|
+
<h2 class="conf-title">{{ title }}</h2>
|
|
12
|
+
<p class="conf-body">{{ message }}</p>
|
|
13
|
+
<div class="conf-actions">
|
|
14
|
+
<button class="conf-btn conf-btn--confirm" (click)="confirm.emit()">{{ confirmLabel }}</button>
|
|
15
|
+
<button class="conf-btn conf-btn--cancel" (click)="cancel.emit()">{{ cancelLabel }}</button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
`,
|
|
19
|
+
styles: [`
|
|
20
|
+
.conf-shell{display:flex;flex-direction:column;align-items:center;gap:18px;padding:20px;text-align:center}
|
|
21
|
+
.conf-icon{font-size:44px}
|
|
22
|
+
.conf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
23
|
+
.conf-body{font-size:14px;color:#a8bfd8;line-height:1.6;margin:0;max-width:360px}
|
|
24
|
+
.conf-actions{display:flex;gap:12px;margin-top:4px}
|
|
25
|
+
.conf-btn{border:none;border-radius:8px;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:.08em;padding:11px 24px;cursor:pointer;transition:opacity .2s}
|
|
26
|
+
.conf-btn--confirm{background:linear-gradient(135deg,#ff4444,#cc0000);color:#fff}
|
|
27
|
+
.conf-btn--cancel{background:transparent;border:1px solid rgba(0,200,255,.3);color:#00c8ff}
|
|
28
|
+
`]
|
|
29
|
+
})
|
|
30
|
+
export class ConfirmationComponent {
|
|
31
|
+
@Input() title = 'Are you sure?';
|
|
32
|
+
@Input() message = 'This action cannot be undone.';
|
|
33
|
+
@Input() icon = '⚠️';
|
|
34
|
+
@Input() confirmLabel = 'Confirm';
|
|
35
|
+
@Input() cancelLabel = 'Cancel';
|
|
36
|
+
@Output() confirm = new EventEmitter<void>();
|
|
37
|
+
@Output() cancel = new EventEmitter<void>();
|
|
38
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'oasis-contact',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule, FormsModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="ct-shell">
|
|
11
|
+
<div class="ct-header">
|
|
12
|
+
<h2 class="ct-title">Contact Us</h2>
|
|
13
|
+
<p class="ct-sub">Get in touch with the OASIS team.</p>
|
|
14
|
+
</div>
|
|
15
|
+
@if (sent()) {
|
|
16
|
+
<div class="oasis-auth-success">✅ Message sent! We'll be in touch soon.</div>
|
|
17
|
+
} @else {
|
|
18
|
+
@if (error()) { <div class="oasis-auth-error">{{ error() }}</div> }
|
|
19
|
+
<div class="oasis-grid-2">
|
|
20
|
+
<div class="oasis-field"><label class="oasis-label">First Name</label><input class="oasis-input" type="text" [(ngModel)]="firstName" placeholder="John" /></div>
|
|
21
|
+
<div class="oasis-field"><label class="oasis-label">Last Name</label><input class="oasis-input" type="text" [(ngModel)]="lastName" placeholder="Doe" /></div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="oasis-field"><label class="oasis-label">Email</label><input class="oasis-input" type="email" [(ngModel)]="email" placeholder="name@example.com" /></div>
|
|
24
|
+
<div class="oasis-field"><label class="oasis-label">Subject</label><input class="oasis-input" type="text" [(ngModel)]="subject" placeholder="How can we help?" /></div>
|
|
25
|
+
<div class="oasis-field"><label class="oasis-label">Message</label><textarea class="oasis-input oasis-textarea" [(ngModel)]="message" placeholder="Your message…" rows="4"></textarea></div>
|
|
26
|
+
<button class="oasis-btn" [disabled]="loading()" (click)="submit()">{{ loading() ? 'Sending…' : 'Send Message' }}</button>
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
29
|
+
`,
|
|
30
|
+
styles: [`
|
|
31
|
+
.ct-shell{display:flex;flex-direction:column;gap:18px}
|
|
32
|
+
.ct-header{text-align:center}
|
|
33
|
+
.ct-title{font-family:'Orbitron',sans-serif;font-size:20px;color:#fff;margin:0 0 6px}
|
|
34
|
+
.ct-sub{font-size:13px;color:#7a9bbf;margin:0}
|
|
35
|
+
.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}
|
|
36
|
+
.oasis-auth-success{background:rgba(72,220,130,.12);border:1px solid rgba(72,220,130,.3);color:#48dc82;border-radius:8px;padding:10px 14px;font-size:13px}
|
|
37
|
+
.oasis-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
38
|
+
.oasis-field{display:flex;flex-direction:column;gap:6px}
|
|
39
|
+
.oasis-label{font-size:12px;font-weight:600;letter-spacing:.06em;color:#7a9bbf;text-transform:uppercase}
|
|
40
|
+
.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;font-family:inherit}
|
|
41
|
+
.oasis-input:focus{border-color:rgba(0,200,255,.5)}
|
|
42
|
+
.oasis-textarea{resize:vertical;min-height:100px}
|
|
43
|
+
.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}
|
|
44
|
+
.oasis-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
45
|
+
`]
|
|
46
|
+
})
|
|
47
|
+
export class ContactComponent {
|
|
48
|
+
firstName = ''; lastName = ''; email = ''; subject = ''; message = '';
|
|
49
|
+
loading = signal(false); error = signal(''); sent = signal(false);
|
|
50
|
+
|
|
51
|
+
async submit() {
|
|
52
|
+
if (!this.email || !this.message) { this.error.set('Please fill in your email and message.'); return; }
|
|
53
|
+
this.loading.set(true); this.error.set('');
|
|
54
|
+
try {
|
|
55
|
+
await fetch(`https://formsubmit.co/ajax/davidellams@hotmail.com`, {
|
|
56
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ firstName: this.firstName, lastName: this.lastName, email: this.email, subject: this.subject, message: this.message })
|
|
58
|
+
});
|
|
59
|
+
this.sent.set(true);
|
|
60
|
+
} catch (e: any) {
|
|
61
|
+
this.error.set(e?.message ?? 'Send failed. Please try again.');
|
|
62
|
+
} finally { this.loading.set(false); }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'oasis-add-data',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [CommonModule, FormsModule],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="mf-shell">
|
|
11
|
+
<div class="mf-header"><span>➕</span><h2 class="mf-title">Add Data</h2></div>
|
|
12
|
+
<div class="mf-body">
|
|
13
|
+
<div class="mf-field"><label class="mf-lbl">Data Name</label><input class="mf-inp" [(ngModel)]="form['dataname']" placeholder="Data Name..." /></div>
|
|
14
|
+
<div class="mf-field"><label class="mf-lbl">Data Type</label><input class="mf-inp" [(ngModel)]="form['datatype']" placeholder="Data Type..." /></div>
|
|
15
|
+
<div class="mf-field"><label class="mf-lbl">Value</label><input class="mf-inp" [(ngModel)]="form['value']" placeholder="Value..." /></div>
|
|
16
|
+
<div class="mf-field"><label class="mf-lbl">Provider</label><input class="mf-inp" [(ngModel)]="form['provider']" placeholder="Provider..." /></div>
|
|
17
|
+
<div class="mf-field"><label class="mf-lbl">Description</label><input class="mf-inp" [(ngModel)]="form['description']" placeholder="Description..." /></div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="mf-footer">
|
|
20
|
+
<button class="mf-save" (click)="save()">Save</button>
|
|
21
|
+
<button class="mf-cancel" (click)="close.emit()">Cancel</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
`,
|
|
25
|
+
styles: [`
|
|
26
|
+
.mf-shell{display:flex;flex-direction:column;gap:18px;padding:20px}
|
|
27
|
+
.mf-header{display:flex;align-items:center;gap:10px}
|
|
28
|
+
.mf-title{font-family:'Orbitron',sans-serif;font-size:18px;color:#fff;margin:0}
|
|
29
|
+
.mf-body{display:flex;flex-direction:column;gap:12px}
|
|
30
|
+
.mf-field{display:flex;flex-direction:column;gap:5px}
|
|
31
|
+
.mf-lbl{font-size:11px;font-weight:700;letter-spacing:.07em;color:#7a9bbf;text-transform:uppercase}
|
|
32
|
+
.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}
|
|
33
|
+
.mf-inp:focus{border-color:rgba(0,200,255,.6)}
|
|
34
|
+
.mf-footer{display:flex;gap:10px;justify-content:flex-end}
|
|
35
|
+
.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}
|
|
36
|
+
.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}
|
|
37
|
+
`]
|
|
38
|
+
})
|
|
39
|
+
export class AddDataComponent {
|
|
40
|
+
@Input() show = false;
|
|
41
|
+
@Output() close = new EventEmitter<void>();
|
|
42
|
+
form: Record<string,string> = {
|
|
43
|
+
'dataname': '',
|
|
44
|
+
'datatype': '',
|
|
45
|
+
'value': '',
|
|
46
|
+
'provider': '',
|
|
47
|
+
'description': ''
|
|
48
|
+
};
|
|
49
|
+
save() { this.close.emit(); }
|
|
50
|
+
}
|