@oasisomniverse/react 1.0.0 → 1.1.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/README.md +68 -53
- package/package.json +72 -72
- package/src/components/common/AvatarConnect.jsx +89 -89
- package/src/components/common/Contact.jsx +60 -60
- package/src/components/common/Eggs.jsx +50 -50
- package/src/components/common/Game.jsx +36 -36
- package/src/components/common/HyperDrive.jsx +50 -50
- package/src/components/common/KarmaToast.jsx +38 -38
- package/src/components/common/Map.jsx +42 -42
- package/src/components/common/MenuMessage.jsx +70 -70
- package/src/components/common/Messaging.jsx +74 -74
- package/src/components/common/Mission.jsx +40 -40
- package/src/components/common/NFT.jsx +42 -42
- package/src/components/common/NavBar.jsx +52 -52
- package/src/components/common/OApp.jsx +41 -41
- package/src/components/common/ONET.jsx +50 -50
- package/src/components/common/ONODE.jsx +49 -49
- package/src/components/common/OasisModal.css +9 -9
- package/src/components/common/OasisModal.jsx +29 -29
- package/src/components/common/ProviderDropdown.jsx +61 -61
- package/src/components/common/Providers.jsx +38 -38
- package/src/components/common/Quest.jsx +40 -40
- package/src/components/common/SearchAvatars.jsx +49 -49
- package/src/components/common/Seeds.jsx +39 -39
- package/src/components/common/Settings.jsx +70 -70
- package/src/components/common/StarField.jsx +56 -56
- package/src/components/common/Wallet.jsx +45 -45
- package/src/components/index.js +26 -26
package/README.md
CHANGED
|
@@ -1,69 +1,84 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @oasisomniverse/react — OASIS React UI Component Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The React UI component library for the [OASIS Platform](https://oasisomniverse.one). 126 components covering avatar SSO, karma, NFTs, quests, map, seeds, messaging, OApps, providers and more — ready to drop into any React or Next.js project.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@oasisomniverse/react)
|
|
6
|
+
[](https://react.oportal.oasisomniverse.one)
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Install
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- [Contributing Guide](Docs/smart_commits.md) - How to contribute using smart commits
|
|
12
|
-
|
|
13
|
-
## Project Structure
|
|
14
|
-
```
|
|
15
|
-
├── react-app
|
|
16
|
-
│ ├── public
|
|
17
|
-
│ │ └── index.html
|
|
18
|
-
│ ├── src
|
|
19
|
-
| | ├── assets
|
|
20
|
-
| | | ├── images
|
|
21
|
-
| | | ├── scss
|
|
22
|
-
| | ├── components
|
|
23
|
-
| | | ├── common
|
|
24
|
-
| | | | ├── sidebar
|
|
25
|
-
| | | ├── popups
|
|
26
|
-
│ │ └── index.js
|
|
10
|
+
```bash
|
|
11
|
+
npm install @oasisomniverse/react
|
|
27
12
|
```
|
|
28
13
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `index.html` is the main html file
|
|
32
|
-
- `assets` folder contains all assets files for each component like images and css/scss files.
|
|
33
|
-
- `components` folder contains different components used in the app.
|
|
34
|
-
- `components/common` folder contains different components tha are common like sidebar, navbar etc.
|
|
35
|
-
- `components/popups` folder contains different popup components.
|
|
36
|
-
- `index.js` is the main javaScript file needed for the react-app.
|
|
37
|
-
|
|
38
|
-
## CSS Syntax rules
|
|
39
|
-
Please follow these rules so that the other CSS classes does not gets messed up.
|
|
40
|
-
|
|
41
|
-
For example if you create a component called `Login.js` It's outer most tag element should have a class of `login` and the child elements of login should have a class name started with `login-`
|
|
14
|
+
## Basic Usage
|
|
42
15
|
|
|
43
|
-
|
|
16
|
+
```jsx
|
|
17
|
+
import { Login, AvatarConnect, KarmaToast } from '@oasisomniverse/react';
|
|
44
18
|
|
|
45
|
-
|
|
19
|
+
const App = () => (
|
|
20
|
+
<div>
|
|
21
|
+
<Login apiUrl="https://api.web4.oasisomniverse.one" onSuccess={(avatar) => console.log(avatar)} />
|
|
22
|
+
<AvatarConnect />
|
|
23
|
+
<KarmaToast message="+10 karma earned" value={10} />
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
46
26
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
npm i
|
|
27
|
+
export default App;
|
|
51
28
|
```
|
|
52
|
-
This will install all the third-part components used in the project
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
30
|
+
## Component List
|
|
31
|
+
|
|
32
|
+
All 126 components are grouped below. Every component is also available in vanilla JS, Angular, Vue, Svelte and Next.js at full parity — see the [OASIS Web Component Library](https://oportal.oasisomniverse.one) for the full matrix.
|
|
33
|
+
|
|
34
|
+
| Group | Components |
|
|
35
|
+
|---|---|
|
|
36
|
+
| **Auth & Identity** | AcceptInvite, AvatarConnect, ForgotPassword, Login, ResetPassword, SearchAvatar, SearchAvatars, SendInvite, Signup, VerifyEmail |
|
|
37
|
+
| **Avatar** | AvatarProfile, AvatarWallet, EditAvatar, SearchProfiles, ViewAchievements, ViewAvatar, ViewAvatarKarma, ViewLeagues, ViewOrganizations, ViewTournaments |
|
|
38
|
+
| **Data Screen** | ActivityPub, AddData, EOSIO, Ethereum, Holochain, IPFS, LoadData, ManageData, MongoDB, Neo4j, OffChainManagement, SearchData, Solana, Solid, SQLite, ThreeFold |
|
|
39
|
+
| **Eggs** | Eggs, ManageEggs, SearchEggs, ViewEggs |
|
|
40
|
+
| **Game** | Game |
|
|
41
|
+
| **Karma** | KarmaPanel, KarmaToast, SearchKarma, ViewKarma, VoteKarma |
|
|
42
|
+
| **Map** | Add2DObjectToMap, Add3DObjectToMap, AddQuestToMap, DownloadMap, ManageMap, Map, PlotRouteOnMap, SearchMap, ViewGlobal3DMap, ViewHalonsOnMap, ViewOAppOnMap, ViewQuestOnMap |
|
|
43
|
+
| **Messages** | MenuMessage, Message, MessageContacts, Messaging |
|
|
44
|
+
| **Mission** | ManageMission, Mission, SearchMission, ViewMission |
|
|
45
|
+
| **NFT** | ContactPopupNFT, ManageNFT, NFT, PurchaseNFT, PurchaseVirtualLandNFT, SearchNFT, ViewNFT |
|
|
46
|
+
| **OApp** | CreateOApp, DeployOApp, DownloadOApp, EditOApp, InstallOApp, LaunchOApp, ManageOApp, OApp, SearchOApp |
|
|
47
|
+
| **Providers** | CompareProviderSpeeds, CrossChainManagement, ManageAutoFailover, ManageAutoReplication, ManageLoadBalancing, ManageProviders, ProviderDropdown, Providers, SearchProviders, SeedsProvider, ViewProviderStats, ViewProviders |
|
|
48
|
+
| **Quest** | ManageQuest, Quest, SearchQuest, ViewQuest |
|
|
49
|
+
| **Seeds** | DonateSeeds, ManageSeeds, PayWithSeeds, RewardSeeds, SearchSeeds, Seeds, ViewSeeds |
|
|
50
|
+
| **Common UI** | ComingSoon, Confirmation, Contact, HyperDrive, NavBar, ONET, ONODE, OasisModal, Settings, SideNav, StarField, Wallet |
|
|
51
|
+
|
|
52
|
+
## Dark Space Design System
|
|
53
|
+
|
|
54
|
+
The OASIS component library ships with the **Dark Space** design system — a deep-space aesthetic built for the OASIS Omniverse:
|
|
55
|
+
|
|
56
|
+
- **Background**: near-black (#0a0d14) with subtle nebula gradients
|
|
57
|
+
- **Primary accent**: electric cyan (#00c8ff)
|
|
58
|
+
- **Typography**: [Orbitron](https://fonts.google.com/specimen/Orbitron) for headings, [Rajdhani](https://fonts.google.com/specimen/Rajdhani) for body text
|
|
59
|
+
- **Text**: always bright (#e0f0ff / #fff) — never dim or faded
|
|
60
|
+
- **Borders**: translucent cyan (`rgba(0,200,255,0.2)`)
|
|
61
|
+
- **Cards**: glassy dark panels with backdrop-filter blur
|
|
62
|
+
|
|
63
|
+
Override the CSS custom properties to theme components for your own OAPP:
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
:root {
|
|
67
|
+
--oasis-bg: #0a0d14;
|
|
68
|
+
--oasis-accent: #00c8ff;
|
|
69
|
+
--oasis-text: #e0f0ff;
|
|
70
|
+
--oasis-border: rgba(0, 200, 255, 0.2);
|
|
71
|
+
}
|
|
57
72
|
```
|
|
58
73
|
|
|
59
|
-
##
|
|
74
|
+
## Live Demo
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
**[react.oportal.oasisomniverse.one](https://react.oportal.oasisomniverse.one)** — the React OPORTAL running all 126 components live.
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
REACT_APP_API_URL=https://your-api-host/api
|
|
67
|
-
```
|
|
78
|
+
## Links
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
- [GitHub](https://github.com/NextGenSoftwareUK/OASIS-React-UI-Component-Library)
|
|
81
|
+
- [npm](https://www.npmjs.com/package/@oasisomniverse/react)
|
|
82
|
+
- [OASIS API Docs](https://oasis-web4.gitbook.io/oasis-web4-docs/)
|
|
83
|
+
- [Developer Portal](https://oportal.oasisomniverse.one)
|
|
84
|
+
- [OASIS Platform](https://oasisomniverse.one)
|
package/package.json
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@oasisomniverse/react",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "OASIS Omniverse React UI components",
|
|
5
|
-
"keywords": ["oasis", "omniverse", "react", "components"],
|
|
6
|
-
"author": "NextGenSoftware",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://github.com/NextGenSoftwareUK/OASIS-React-UI-Component-Library.git"
|
|
11
|
-
},
|
|
12
|
-
"main": "src/components/index.js",
|
|
13
|
-
"module": "src/components/index.js",
|
|
14
|
-
"exports": {
|
|
15
|
-
".": "./src/components/index.js",
|
|
16
|
-
"./components/*": "./src/components/*.jsx"
|
|
17
|
-
},
|
|
18
|
-
"files": ["src/components"],
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@devexpress/dx-react-core": "^2.7.6",
|
|
21
|
-
"@material-ui/core": "^4.12.3",
|
|
22
|
-
"@material-ui/icons": "^4.11.2",
|
|
23
|
-
"@testing-library/jest-dom": "^5.14.1",
|
|
24
|
-
"@testing-library/react": "^11.2.7",
|
|
25
|
-
"@testing-library/user-event": "^12.8.3",
|
|
26
|
-
"ag-grid-community": "^26.0.0",
|
|
27
|
-
"ag-grid-react": "^26.0.0",
|
|
28
|
-
"axios": "^0.21.1",
|
|
29
|
-
"bootstrap": "^4.6.0",
|
|
30
|
-
"font-awesome": "^4.7.0",
|
|
31
|
-
"formik": "^2.2.9",
|
|
32
|
-
"oasis-api": "^0.0.3",
|
|
33
|
-
"react": "^19.2.7",
|
|
34
|
-
"react-bootstrap": "^1.6.3",
|
|
35
|
-
"react-dom": "^19.2.7",
|
|
36
|
-
"react-dual-listbox": "^2.2.0",
|
|
37
|
-
"react-loader-spinner": "^4.0.0",
|
|
38
|
-
"react-router-dom": "^5.2.1",
|
|
39
|
-
"react-slide-toggle": "^0.3.5",
|
|
40
|
-
"react-toastify": "^8.0.3",
|
|
41
|
-
"web-vitals": "^1.1.2",
|
|
42
|
-
"yup": "^0.32.9"
|
|
43
|
-
},
|
|
44
|
-
"scripts": {
|
|
45
|
-
"start": "vite",
|
|
46
|
-
"build": "vite build",
|
|
47
|
-
"preview": "vite preview"
|
|
48
|
-
},
|
|
49
|
-
"engines": {
|
|
50
|
-
"node": "22.x"
|
|
51
|
-
},
|
|
52
|
-
"browserslist": {
|
|
53
|
-
"production": [
|
|
54
|
-
">0.2%",
|
|
55
|
-
"not dead",
|
|
56
|
-
"not op_mini all"
|
|
57
|
-
],
|
|
58
|
-
"development": [
|
|
59
|
-
"last 1 chrome version",
|
|
60
|
-
"last 1 firefox version",
|
|
61
|
-
"last 1 safari version"
|
|
62
|
-
]
|
|
63
|
-
},
|
|
64
|
-
"devDependencies": {
|
|
65
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
66
|
-
"sass": "^1.100.0",
|
|
67
|
-
"vite": "^6.3.5"
|
|
68
|
-
},
|
|
69
|
-
"publishConfig": {
|
|
70
|
-
"access": "public"
|
|
71
|
-
}
|
|
72
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@oasisomniverse/react",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "OASIS Omniverse React UI components",
|
|
5
|
+
"keywords": ["oasis", "omniverse", "react", "components"],
|
|
6
|
+
"author": "NextGenSoftware",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/NextGenSoftwareUK/OASIS-React-UI-Component-Library.git"
|
|
11
|
+
},
|
|
12
|
+
"main": "src/components/index.js",
|
|
13
|
+
"module": "src/components/index.js",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/components/index.js",
|
|
16
|
+
"./components/*": "./src/components/*.jsx"
|
|
17
|
+
},
|
|
18
|
+
"files": ["src/components"],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@devexpress/dx-react-core": "^2.7.6",
|
|
21
|
+
"@material-ui/core": "^4.12.3",
|
|
22
|
+
"@material-ui/icons": "^4.11.2",
|
|
23
|
+
"@testing-library/jest-dom": "^5.14.1",
|
|
24
|
+
"@testing-library/react": "^11.2.7",
|
|
25
|
+
"@testing-library/user-event": "^12.8.3",
|
|
26
|
+
"ag-grid-community": "^26.0.0",
|
|
27
|
+
"ag-grid-react": "^26.0.0",
|
|
28
|
+
"axios": "^0.21.1",
|
|
29
|
+
"bootstrap": "^4.6.0",
|
|
30
|
+
"font-awesome": "^4.7.0",
|
|
31
|
+
"formik": "^2.2.9",
|
|
32
|
+
"oasis-api": "^0.0.3",
|
|
33
|
+
"react": "^19.2.7",
|
|
34
|
+
"react-bootstrap": "^1.6.3",
|
|
35
|
+
"react-dom": "^19.2.7",
|
|
36
|
+
"react-dual-listbox": "^2.2.0",
|
|
37
|
+
"react-loader-spinner": "^4.0.0",
|
|
38
|
+
"react-router-dom": "^5.2.1",
|
|
39
|
+
"react-slide-toggle": "^0.3.5",
|
|
40
|
+
"react-toastify": "^8.0.3",
|
|
41
|
+
"web-vitals": "^1.1.2",
|
|
42
|
+
"yup": "^0.32.9"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"start": "vite",
|
|
46
|
+
"build": "vite build",
|
|
47
|
+
"preview": "vite preview"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": "22.x"
|
|
51
|
+
},
|
|
52
|
+
"browserslist": {
|
|
53
|
+
"production": [
|
|
54
|
+
">0.2%",
|
|
55
|
+
"not dead",
|
|
56
|
+
"not op_mini all"
|
|
57
|
+
],
|
|
58
|
+
"development": [
|
|
59
|
+
"last 1 chrome version",
|
|
60
|
+
"last 1 firefox version",
|
|
61
|
+
"last 1 safari version"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
66
|
+
"sass": "^1.100.0",
|
|
67
|
+
"vite": "^6.3.5"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import OasisModal from './OasisModal';
|
|
3
|
-
|
|
4
|
-
const API_URL = 'https://api.web4.oasisomniverse.one';
|
|
5
|
-
const SESSION_KEY = 'oasis_session';
|
|
6
|
-
|
|
7
|
-
function loadSession() {
|
|
8
|
-
try { return JSON.parse(sessionStorage.getItem(SESSION_KEY)); } catch { return null; }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function AvatarConnect({ onLogin, onLogout, sessionKey = SESSION_KEY, apiUrl = API_URL }) {
|
|
12
|
-
const [session, setSession] = useState(loadSession);
|
|
13
|
-
const [open, setOpen] = useState(false);
|
|
14
|
-
const [username, setUsername] = useState('');
|
|
15
|
-
const [password, setPassword] = useState('');
|
|
16
|
-
const [showPwd, setShowPwd] = useState(false);
|
|
17
|
-
const [loading, setLoading] = useState(false);
|
|
18
|
-
const [error, setError] = useState('');
|
|
19
|
-
|
|
20
|
-
const doLogin = useCallback(async () => {
|
|
21
|
-
if (!username || !password) { setError('Please fill in all fields.'); return; }
|
|
22
|
-
setLoading(true); setError('');
|
|
23
|
-
try {
|
|
24
|
-
const { OASISClient } = await import('@oasisomniverse/web4-api');
|
|
25
|
-
const oasis = new OASISClient({ baseUrl: apiUrl });
|
|
26
|
-
const result = await oasis.auth.login({ username, password });
|
|
27
|
-
const karma = await oasis.karma.getKarmaForAvatar({ avatarId: result.avatarId });
|
|
28
|
-
const sess = { avatarId: result.avatarId, username, karma: karma.total ?? 0 };
|
|
29
|
-
sessionStorage.setItem(sessionKey, JSON.stringify(sess));
|
|
30
|
-
setSession(sess); setOpen(false);
|
|
31
|
-
onLogin?.(sess);
|
|
32
|
-
} catch (e) {
|
|
33
|
-
setError(e?.message ?? 'Login failed. Please check your credentials.');
|
|
34
|
-
} finally {
|
|
35
|
-
setLoading(false);
|
|
36
|
-
}
|
|
37
|
-
}, [username, password, apiUrl, sessionKey, onLogin]);
|
|
38
|
-
|
|
39
|
-
const doLogout = useCallback(() => {
|
|
40
|
-
sessionStorage.removeItem(sessionKey);
|
|
41
|
-
setSession(null);
|
|
42
|
-
onLogout?.();
|
|
43
|
-
}, [sessionKey, onLogout]);
|
|
44
|
-
|
|
45
|
-
if (session) {
|
|
46
|
-
return (
|
|
47
|
-
<div style={{ display:'flex', alignItems:'center', gap:10, background:'rgba(0,200,255,.08)', border:'1px solid rgba(0,200,255,.2)', borderRadius:999, padding:'6px 14px 6px 10px', fontSize:13 }}>
|
|
48
|
-
<span style={{ fontSize:18 }}>👤</span>
|
|
49
|
-
<span style={{ color:'#fff', fontWeight:600 }}>{session.username}</span>
|
|
50
|
-
<span style={{ color:'#00c8ff', fontFamily:"'Orbitron',sans-serif", fontSize:11 }}>⚡ {session.karma ?? 0}</span>
|
|
51
|
-
<button onClick={doLogout} style={{ background:'none', border:'1px solid rgba(255,100,100,.3)', color:'#ff8080', borderRadius:6, fontSize:11, padding:'3px 8px', cursor:'pointer', marginLeft:4 }}>Logout</button>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<>
|
|
58
|
-
<button onClick={() => setOpen(true)} style={{ background:'linear-gradient(135deg,#00c8ff,#0080ff)', border:'none', borderRadius:8, color:'#fff', fontFamily:"'Orbitron',sans-serif", fontSize:12, fontWeight:700, letterSpacing:'.08em', padding:'9px 18px', cursor:'pointer' }}>
|
|
59
|
-
Beam In
|
|
60
|
-
</button>
|
|
61
|
-
|
|
62
|
-
<OasisModal open={open} onClose={() => setOpen(false)} title="Beam In">
|
|
63
|
-
<div style={{ display:'flex', flexDirection:'column', gap:16 }}>
|
|
64
|
-
{error && <div style={{ background:'rgba(255,80,80,.12)', border:'1px solid rgba(255,80,80,.3)', color:'#ff6b6b', borderRadius:8, padding:'10px 14px', fontSize:13 }}>{error}</div>}
|
|
65
|
-
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
66
|
-
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Username</label>
|
|
67
|
-
<input value={username} onChange={e => setUsername(e.target.value)} type="text" placeholder="yourusername" autoComplete="username"
|
|
68
|
-
style={{ background:'rgba(255,255,255,.05)', border:'1px solid rgba(0,200,255,.2)', borderRadius:8, padding:'10px 14px', color:'#fff', fontSize:14, outline:'none' }} />
|
|
69
|
-
</div>
|
|
70
|
-
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
71
|
-
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Password</label>
|
|
72
|
-
<div style={{ position:'relative' }}>
|
|
73
|
-
<input value={password} onChange={e => setPassword(e.target.value)} type={showPwd ? 'text' : 'password'} placeholder="••••••••" autoComplete="current-password"
|
|
74
|
-
onKeyDown={e => e.key === 'Enter' && doLogin()}
|
|
75
|
-
style={{ width:'100%', background:'rgba(255,255,255,.05)', border:'1px solid rgba(0,200,255,.2)', borderRadius:8, padding:'10px 44px 10px 14px', color:'#fff', fontSize:14, outline:'none', boxSizing:'border-box' }} />
|
|
76
|
-
<button onClick={() => setShowPwd(v => !v)} type="button" style={{ position:'absolute', right:10, top:'50%', transform:'translateY(-50%)', background:'none', border:'none', cursor:'pointer', fontSize:16, padding:0, lineHeight:1 }}>
|
|
77
|
-
{showPwd ? '🙈' : '👁️'}
|
|
78
|
-
</button>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
<button onClick={doLogin} disabled={loading}
|
|
82
|
-
style={{ background:'linear-gradient(135deg,#00c8ff,#0080ff)', border:'none', borderRadius:8, color:'#fff', fontFamily:"'Orbitron',sans-serif", fontSize:13, fontWeight:700, letterSpacing:'.08em', padding:12, cursor:'pointer', opacity: loading ? .5 : 1 }}>
|
|
83
|
-
{loading ? 'Connecting…' : 'Beam In'}
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
86
|
-
</OasisModal>
|
|
87
|
-
</>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import OasisModal from './OasisModal';
|
|
3
|
+
|
|
4
|
+
const API_URL = 'https://api.web4.oasisomniverse.one';
|
|
5
|
+
const SESSION_KEY = 'oasis_session';
|
|
6
|
+
|
|
7
|
+
function loadSession() {
|
|
8
|
+
try { return JSON.parse(sessionStorage.getItem(SESSION_KEY)); } catch { return null; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function AvatarConnect({ onLogin, onLogout, sessionKey = SESSION_KEY, apiUrl = API_URL }) {
|
|
12
|
+
const [session, setSession] = useState(loadSession);
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
const [username, setUsername] = useState('');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const [showPwd, setShowPwd] = useState(false);
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
const [error, setError] = useState('');
|
|
19
|
+
|
|
20
|
+
const doLogin = useCallback(async () => {
|
|
21
|
+
if (!username || !password) { setError('Please fill in all fields.'); return; }
|
|
22
|
+
setLoading(true); setError('');
|
|
23
|
+
try {
|
|
24
|
+
const { OASISClient } = await import('@oasisomniverse/web4-api');
|
|
25
|
+
const oasis = new OASISClient({ baseUrl: apiUrl });
|
|
26
|
+
const result = await oasis.auth.login({ username, password });
|
|
27
|
+
const karma = await oasis.karma.getKarmaForAvatar({ avatarId: result.avatarId });
|
|
28
|
+
const sess = { avatarId: result.avatarId, username, karma: karma.total ?? 0 };
|
|
29
|
+
sessionStorage.setItem(sessionKey, JSON.stringify(sess));
|
|
30
|
+
setSession(sess); setOpen(false);
|
|
31
|
+
onLogin?.(sess);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
setError(e?.message ?? 'Login failed. Please check your credentials.');
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}, [username, password, apiUrl, sessionKey, onLogin]);
|
|
38
|
+
|
|
39
|
+
const doLogout = useCallback(() => {
|
|
40
|
+
sessionStorage.removeItem(sessionKey);
|
|
41
|
+
setSession(null);
|
|
42
|
+
onLogout?.();
|
|
43
|
+
}, [sessionKey, onLogout]);
|
|
44
|
+
|
|
45
|
+
if (session) {
|
|
46
|
+
return (
|
|
47
|
+
<div style={{ display:'flex', alignItems:'center', gap:10, background:'rgba(0,200,255,.08)', border:'1px solid rgba(0,200,255,.2)', borderRadius:999, padding:'6px 14px 6px 10px', fontSize:13 }}>
|
|
48
|
+
<span style={{ fontSize:18 }}>👤</span>
|
|
49
|
+
<span style={{ color:'#fff', fontWeight:600 }}>{session.username}</span>
|
|
50
|
+
<span style={{ color:'#00c8ff', fontFamily:"'Orbitron',sans-serif", fontSize:11 }}>⚡ {session.karma ?? 0}</span>
|
|
51
|
+
<button onClick={doLogout} style={{ background:'none', border:'1px solid rgba(255,100,100,.3)', color:'#ff8080', borderRadius:6, fontSize:11, padding:'3px 8px', cursor:'pointer', marginLeft:4 }}>Logout</button>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<button onClick={() => setOpen(true)} style={{ background:'linear-gradient(135deg,#00c8ff,#0080ff)', border:'none', borderRadius:8, color:'#fff', fontFamily:"'Orbitron',sans-serif", fontSize:12, fontWeight:700, letterSpacing:'.08em', padding:'9px 18px', cursor:'pointer' }}>
|
|
59
|
+
Beam In
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<OasisModal open={open} onClose={() => setOpen(false)} title="Beam In">
|
|
63
|
+
<div style={{ display:'flex', flexDirection:'column', gap:16 }}>
|
|
64
|
+
{error && <div style={{ background:'rgba(255,80,80,.12)', border:'1px solid rgba(255,80,80,.3)', color:'#ff6b6b', borderRadius:8, padding:'10px 14px', fontSize:13 }}>{error}</div>}
|
|
65
|
+
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
66
|
+
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Username</label>
|
|
67
|
+
<input value={username} onChange={e => setUsername(e.target.value)} type="text" placeholder="yourusername" autoComplete="username"
|
|
68
|
+
style={{ background:'rgba(255,255,255,.05)', border:'1px solid rgba(0,200,255,.2)', borderRadius:8, padding:'10px 14px', color:'#fff', fontSize:14, outline:'none' }} />
|
|
69
|
+
</div>
|
|
70
|
+
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
71
|
+
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Password</label>
|
|
72
|
+
<div style={{ position:'relative' }}>
|
|
73
|
+
<input value={password} onChange={e => setPassword(e.target.value)} type={showPwd ? 'text' : 'password'} placeholder="••••••••" autoComplete="current-password"
|
|
74
|
+
onKeyDown={e => e.key === 'Enter' && doLogin()}
|
|
75
|
+
style={{ width:'100%', background:'rgba(255,255,255,.05)', border:'1px solid rgba(0,200,255,.2)', borderRadius:8, padding:'10px 44px 10px 14px', color:'#fff', fontSize:14, outline:'none', boxSizing:'border-box' }} />
|
|
76
|
+
<button onClick={() => setShowPwd(v => !v)} type="button" style={{ position:'absolute', right:10, top:'50%', transform:'translateY(-50%)', background:'none', border:'none', cursor:'pointer', fontSize:16, padding:0, lineHeight:1 }}>
|
|
77
|
+
{showPwd ? '🙈' : '👁️'}
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<button onClick={doLogin} disabled={loading}
|
|
82
|
+
style={{ background:'linear-gradient(135deg,#00c8ff,#0080ff)', border:'none', borderRadius:8, color:'#fff', fontFamily:"'Orbitron',sans-serif", fontSize:13, fontWeight:700, letterSpacing:'.08em', padding:12, cursor:'pointer', opacity: loading ? .5 : 1 }}>
|
|
83
|
+
{loading ? 'Connecting…' : 'Beam In'}
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</OasisModal>
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const s = {
|
|
4
|
-
shell: { display: 'flex', flexDirection: 'column', gap: 18 },
|
|
5
|
-
header: { textAlign: 'center' },
|
|
6
|
-
title: { fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' },
|
|
7
|
-
sub: { fontSize: 13, color: '#7a9bbf', margin: 0 },
|
|
8
|
-
error: { background: 'rgba(255,80,80,.12)', border: '1px solid rgba(255,80,80,.3)', color: '#ff6b6b', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
9
|
-
success: { background: 'rgba(72,220,130,.12)', border: '1px solid rgba(72,220,130,.3)', color: '#48dc82', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
10
|
-
grid2: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 },
|
|
11
|
-
field: { display: 'flex', flexDirection: 'column', gap: 6 },
|
|
12
|
-
label: { fontSize: 12, fontWeight: 600, letterSpacing: '.06em', color: '#7a9bbf', textTransform: 'uppercase' },
|
|
13
|
-
input: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit' },
|
|
14
|
-
textarea: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit', resize: 'vertical', minHeight: 100 },
|
|
15
|
-
btn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 13, fontWeight: 700, letterSpacing: '.08em', padding: 12, cursor: 'pointer' },
|
|
16
|
-
btnDisabled: { opacity: .5, cursor: 'not-allowed' },
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function Contact() {
|
|
20
|
-
const [fields, setFields] = useState({ firstName: '', lastName: '', email: '', subject: '', message: '' });
|
|
21
|
-
const [loading, setLoading] = useState(false);
|
|
22
|
-
const [error, setError] = useState('');
|
|
23
|
-
const [sent, setSent] = useState(false);
|
|
24
|
-
|
|
25
|
-
function set(k) { return e => setFields(f => ({ ...f, [k]: e.target.value })); }
|
|
26
|
-
|
|
27
|
-
async function submit() {
|
|
28
|
-
if (!fields.email || !fields.message) { setError('Please fill in your email and message.'); return; }
|
|
29
|
-
setLoading(true); setError('');
|
|
30
|
-
try {
|
|
31
|
-
await fetch('https://formsubmit.co/ajax/davidellams@hotmail.com', {
|
|
32
|
-
method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
33
|
-
body: JSON.stringify(fields),
|
|
34
|
-
});
|
|
35
|
-
setSent(true);
|
|
36
|
-
} catch (e) { setError(e?.message ?? 'Send failed. Please try again.'); }
|
|
37
|
-
finally { setLoading(false); }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div style={s.shell}>
|
|
42
|
-
<div style={s.header}><h2 style={s.title}>Contact Us</h2><p style={s.sub}>Get in touch with the OASIS team.</p></div>
|
|
43
|
-
{sent ? (
|
|
44
|
-
<div style={s.success}>✅ Message sent! We'll be in touch soon.</div>
|
|
45
|
-
) : (
|
|
46
|
-
<>
|
|
47
|
-
{error && <div style={s.error}>{error}</div>}
|
|
48
|
-
<div style={s.grid2}>
|
|
49
|
-
<div style={s.field}><label style={s.label}>First Name</label><input style={s.input} value={fields.firstName} onChange={set('firstName')} placeholder="John" /></div>
|
|
50
|
-
<div style={s.field}><label style={s.label}>Last Name</label><input style={s.input} value={fields.lastName} onChange={set('lastName')} placeholder="Doe" /></div>
|
|
51
|
-
</div>
|
|
52
|
-
<div style={s.field}><label style={s.label}>Email</label><input style={s.input} type="email" value={fields.email} onChange={set('email')} placeholder="name@example.com" /></div>
|
|
53
|
-
<div style={s.field}><label style={s.label}>Subject</label><input style={s.input} value={fields.subject} onChange={set('subject')} placeholder="How can we help?" /></div>
|
|
54
|
-
<div style={s.field}><label style={s.label}>Message</label><textarea style={s.textarea} value={fields.message} onChange={set('message')} placeholder="Your message…" rows={4} /></div>
|
|
55
|
-
<button style={{ ...s.btn, ...(loading ? s.btnDisabled : {}) }} disabled={loading} onClick={submit}>{loading ? 'Sending…' : 'Send Message'}</button>
|
|
56
|
-
</>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const s = {
|
|
4
|
+
shell: { display: 'flex', flexDirection: 'column', gap: 18 },
|
|
5
|
+
header: { textAlign: 'center' },
|
|
6
|
+
title: { fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' },
|
|
7
|
+
sub: { fontSize: 13, color: '#7a9bbf', margin: 0 },
|
|
8
|
+
error: { background: 'rgba(255,80,80,.12)', border: '1px solid rgba(255,80,80,.3)', color: '#ff6b6b', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
9
|
+
success: { background: 'rgba(72,220,130,.12)', border: '1px solid rgba(72,220,130,.3)', color: '#48dc82', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
10
|
+
grid2: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 },
|
|
11
|
+
field: { display: 'flex', flexDirection: 'column', gap: 6 },
|
|
12
|
+
label: { fontSize: 12, fontWeight: 600, letterSpacing: '.06em', color: '#7a9bbf', textTransform: 'uppercase' },
|
|
13
|
+
input: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit' },
|
|
14
|
+
textarea: { width: '100%', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(0,200,255,.2)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 14, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit', resize: 'vertical', minHeight: 100 },
|
|
15
|
+
btn: { background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 8, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 13, fontWeight: 700, letterSpacing: '.08em', padding: 12, cursor: 'pointer' },
|
|
16
|
+
btnDisabled: { opacity: .5, cursor: 'not-allowed' },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function Contact() {
|
|
20
|
+
const [fields, setFields] = useState({ firstName: '', lastName: '', email: '', subject: '', message: '' });
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState('');
|
|
23
|
+
const [sent, setSent] = useState(false);
|
|
24
|
+
|
|
25
|
+
function set(k) { return e => setFields(f => ({ ...f, [k]: e.target.value })); }
|
|
26
|
+
|
|
27
|
+
async function submit() {
|
|
28
|
+
if (!fields.email || !fields.message) { setError('Please fill in your email and message.'); return; }
|
|
29
|
+
setLoading(true); setError('');
|
|
30
|
+
try {
|
|
31
|
+
await fetch('https://formsubmit.co/ajax/davidellams@hotmail.com', {
|
|
32
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
33
|
+
body: JSON.stringify(fields),
|
|
34
|
+
});
|
|
35
|
+
setSent(true);
|
|
36
|
+
} catch (e) { setError(e?.message ?? 'Send failed. Please try again.'); }
|
|
37
|
+
finally { setLoading(false); }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div style={s.shell}>
|
|
42
|
+
<div style={s.header}><h2 style={s.title}>Contact Us</h2><p style={s.sub}>Get in touch with the OASIS team.</p></div>
|
|
43
|
+
{sent ? (
|
|
44
|
+
<div style={s.success}>✅ Message sent! We'll be in touch soon.</div>
|
|
45
|
+
) : (
|
|
46
|
+
<>
|
|
47
|
+
{error && <div style={s.error}>{error}</div>}
|
|
48
|
+
<div style={s.grid2}>
|
|
49
|
+
<div style={s.field}><label style={s.label}>First Name</label><input style={s.input} value={fields.firstName} onChange={set('firstName')} placeholder="John" /></div>
|
|
50
|
+
<div style={s.field}><label style={s.label}>Last Name</label><input style={s.input} value={fields.lastName} onChange={set('lastName')} placeholder="Doe" /></div>
|
|
51
|
+
</div>
|
|
52
|
+
<div style={s.field}><label style={s.label}>Email</label><input style={s.input} type="email" value={fields.email} onChange={set('email')} placeholder="name@example.com" /></div>
|
|
53
|
+
<div style={s.field}><label style={s.label}>Subject</label><input style={s.input} value={fields.subject} onChange={set('subject')} placeholder="How can we help?" /></div>
|
|
54
|
+
<div style={s.field}><label style={s.label}>Message</label><textarea style={s.textarea} value={fields.message} onChange={set('message')} placeholder="Your message…" rows={4} /></div>
|
|
55
|
+
<button style={{ ...s.btn, ...(loading ? s.btnDisabled : {}) }} disabled={loading} onClick={submit}>{loading ? 'Sending…' : 'Send Message'}</button>
|
|
56
|
+
</>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|