@oasisomniverse/nextjs 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 +29 -0
- package/src/components/ForgetPassword.tsx +127 -0
- package/src/components/Login.tsx +223 -0
- package/src/components/ResetPassword.tsx +177 -0
- package/src/components/Signup.tsx +286 -0
- package/src/components/VerifyEmail.tsx +69 -0
- package/src/components/common/AvatarConnect.tsx +90 -0
- package/src/components/common/Contact.tsx +61 -0
- package/src/components/common/Eggs.tsx +51 -0
- package/src/components/common/Game.tsx +37 -0
- package/src/components/common/HyperDrive.tsx +51 -0
- package/src/components/common/KarmaToast.tsx +39 -0
- package/src/components/common/Map.tsx +43 -0
- package/src/components/common/MenuMessage.tsx +71 -0
- package/src/components/common/Messaging.tsx +75 -0
- package/src/components/common/Mission.tsx +41 -0
- package/src/components/common/NFT.tsx +43 -0
- package/src/components/common/NavBar.tsx +138 -0
- package/src/components/common/OApp.tsx +42 -0
- package/src/components/common/ONET.tsx +51 -0
- package/src/components/common/ONODE.tsx +50 -0
- package/src/components/common/OasisModal.tsx +30 -0
- package/src/components/common/ProviderDropdown.tsx +26 -0
- package/src/components/common/Providers.tsx +39 -0
- package/src/components/common/Quest.tsx +41 -0
- package/src/components/common/SearchAvatars.tsx +50 -0
- package/src/components/common/Seeds.tsx +40 -0
- package/src/components/common/Settings.tsx +71 -0
- package/src/components/common/StarField.tsx +57 -0
- package/src/components/common/Wallet.tsx +46 -0
- package/src/components/popups/avatar/components/AvatarWallet.tsx +170 -0
- package/src/components/popups/avatar/components/EditAvatar.tsx +35 -0
- package/src/components/popups/avatar/components/SearchAvatar.tsx +35 -0
- package/src/components/popups/avatar/components/ViewAvatar.tsx +149 -0
- package/src/components/popups/avatar/index.tsx +40 -0
- package/src/components/popups/comingsoon/ComingSoon.tsx +29 -0
- package/src/components/popups/confirmation/Confirmation.tsx +31 -0
- package/src/components/popups/contact/index.tsx +101 -0
- package/src/components/popups/data-screen/components/AddData.tsx +90 -0
- package/src/components/popups/data-screen/components/CrossChainManagement.tsx +88 -0
- package/src/components/popups/data-screen/components/LoadData.tsx +158 -0
- package/src/components/popups/data-screen/components/ManageData.tsx +35 -0
- package/src/components/popups/data-screen/components/OffChainManagement.tsx +178 -0
- package/src/components/popups/data-screen/components/SearchData.tsx +35 -0
- package/src/components/popups/data-screen/index.tsx +53 -0
- package/src/components/popups/eggs/components/ManageEggs.tsx +35 -0
- package/src/components/popups/eggs/components/SearchEggs.tsx +35 -0
- package/src/components/popups/eggs/components/ViewEggs.tsx +35 -0
- package/src/components/popups/eggs/index.tsx +32 -0
- package/src/components/popups/game/components/SearchProfiles.tsx +35 -0
- package/src/components/popups/game/components/ViewAchievements.tsx +35 -0
- package/src/components/popups/game/components/ViewLeagues.tsx +35 -0
- package/src/components/popups/game/components/ViewTournaments.tsx +35 -0
- package/src/components/popups/game/index.tsx +39 -0
- package/src/components/popups/karma/components/SearchKarma.tsx +35 -0
- package/src/components/popups/karma/components/ViewAvatarKarma.tsx +140 -0
- package/src/components/popups/karma/components/ViewKarma.tsx +104 -0
- package/src/components/popups/karma/components/VoteKarma.tsx +35 -0
- package/src/components/popups/karma/index.tsx +35 -0
- package/src/components/popups/map/components/Add2dObjectMap.tsx +35 -0
- package/src/components/popups/map/components/Add3dObjectMap.tsx +35 -0
- package/src/components/popups/map/components/AddQuestToMap.tsx +35 -0
- package/src/components/popups/map/components/DownloadOurWorld.tsx +35 -0
- package/src/components/popups/map/components/ManageMap.tsx +35 -0
- package/src/components/popups/map/components/PlotRouteOnMap.tsx +35 -0
- package/src/components/popups/map/components/SearchMap.tsx +35 -0
- package/src/components/popups/map/components/ViewGlobal3dMap.tsx +35 -0
- package/src/components/popups/map/components/ViewHalonsOnMap.tsx +35 -0
- package/src/components/popups/map/components/ViewOappOnMap.tsx +35 -0
- package/src/components/popups/map/components/ViewQuestOnMap.tsx +35 -0
- package/src/components/popups/map/index.tsx +80 -0
- package/src/components/popups/messages/Message.tsx +87 -0
- package/src/components/popups/mission/components/ManageMission.tsx +35 -0
- package/src/components/popups/mission/components/SearchMission.tsx +35 -0
- package/src/components/popups/mission/components/ViewMission.tsx +35 -0
- package/src/components/popups/mission/index.tsx +33 -0
- package/src/components/popups/nft/ContactPopup.tsx +147 -0
- package/src/components/popups/nft/Solana.tsx +71 -0
- package/src/components/popups/nft/components/ManageOasisNft.tsx +35 -0
- package/src/components/popups/nft/components/PurchaseOasisNft.tsx +35 -0
- package/src/components/popups/nft/components/PurchaseOasisVirtualLandNft.tsx +35 -0
- package/src/components/popups/nft/components/SearchOasisNft.tsx +35 -0
- package/src/components/popups/nft/components/ViewOasisNft.tsx +35 -0
- package/src/components/popups/nft/index.tsx +46 -0
- package/src/components/popups/oapp/components/CreateOAPP.tsx +35 -0
- package/src/components/popups/oapp/components/DeployOAPP.tsx +35 -0
- package/src/components/popups/oapp/components/DownloadOurWorld.tsx +35 -0
- package/src/components/popups/oapp/components/EditOAPP.tsx +35 -0
- package/src/components/popups/oapp/components/InstallOAPP.tsx +35 -0
- package/src/components/popups/oapp/components/LaunchOAPP.tsx +35 -0
- package/src/components/popups/oapp/components/ManageOAPP.tsx +69 -0
- package/src/components/popups/oapp/components/SearchOAPP.tsx +35 -0
- package/src/components/popups/oapp/index.tsx +63 -0
- package/src/components/popups/provider/components/ActivityPub.tsx +35 -0
- package/src/components/popups/provider/components/CompareProviderSpeeds.tsx +124 -0
- package/src/components/popups/provider/components/Eosio.tsx +35 -0
- package/src/components/popups/provider/components/Ethereum.tsx +35 -0
- package/src/components/popups/provider/components/Holochain.tsx +35 -0
- package/src/components/popups/provider/components/Ipfs.tsx +36 -0
- package/src/components/popups/provider/components/ManageAutoFailOver.tsx +35 -0
- package/src/components/popups/provider/components/ManageAutoReplicaton.tsx +35 -0
- package/src/components/popups/provider/components/ManageLoadBalancing.tsx +35 -0
- package/src/components/popups/provider/components/ManageProviders.tsx +35 -0
- package/src/components/popups/provider/components/MongoDb.tsx +35 -0
- package/src/components/popups/provider/components/Neo4j.tsx +35 -0
- package/src/components/popups/provider/components/SearchProviders.tsx +123 -0
- package/src/components/popups/provider/components/Seeds.tsx +35 -0
- package/src/components/popups/provider/components/Solid.tsx +35 -0
- package/src/components/popups/provider/components/SqlLite.tsx +35 -0
- package/src/components/popups/provider/components/ThreeFold.tsx +35 -0
- package/src/components/popups/provider/components/ViewProviderStats.tsx +124 -0
- package/src/components/popups/provider/components/ViewProviders.tsx +122 -0
- package/src/components/popups/provider/index.tsx +114 -0
- package/src/components/popups/quest/components/ManageQuest.tsx +35 -0
- package/src/components/popups/quest/components/SearchQuest.tsx +35 -0
- package/src/components/popups/quest/components/ViewQuest.tsx +35 -0
- package/src/components/popups/quest/index.tsx +33 -0
- package/src/components/popups/seeds/components/AcceptInvite.tsx +62 -0
- package/src/components/popups/seeds/components/DonateSeeds.tsx +179 -0
- package/src/components/popups/seeds/components/ManageSeeds.tsx +35 -0
- package/src/components/popups/seeds/components/PayWithSeeds.tsx +234 -0
- package/src/components/popups/seeds/components/RewardSeeds.tsx +179 -0
- package/src/components/popups/seeds/components/SearchSeeds.tsx +92 -0
- package/src/components/popups/seeds/components/SendInvite.tsx +179 -0
- package/src/components/popups/seeds/components/ViewOrganizations.tsx +179 -0
- package/src/components/popups/seeds/components/ViewSeeds.tsx +222 -0
- package/src/components/popups/seeds/index.tsx +71 -0
- package/src/index.ts +129 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import ShowIcon from '../assets/images/visible-icon.svg';
|
|
5
|
+
import HideIcon from '../assets/images/hidden-icon.svg';
|
|
6
|
+
|
|
7
|
+
import { toast } from "react-toastify";
|
|
8
|
+
import Loader from "react-loader-spinner";
|
|
9
|
+
import { Modal } from 'react-bootstrap';
|
|
10
|
+
import { Formik } from "formik";
|
|
11
|
+
import * as Yup from "yup";
|
|
12
|
+
import axios from "axios";
|
|
13
|
+
import "../../src/assets/scss/signup.scss";
|
|
14
|
+
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
|
15
|
+
import API_BASE_URL from "../config/api";
|
|
16
|
+
|
|
17
|
+
export default class Signup extends React.Component {
|
|
18
|
+
|
|
19
|
+
constructor(props) {
|
|
20
|
+
super(props);
|
|
21
|
+
|
|
22
|
+
this.state = {
|
|
23
|
+
loading: false,
|
|
24
|
+
form: {
|
|
25
|
+
firstName: '',
|
|
26
|
+
lastName: '',
|
|
27
|
+
email: '',
|
|
28
|
+
password: '',
|
|
29
|
+
confirmPassword: '',
|
|
30
|
+
acceptTerms: false,
|
|
31
|
+
avatarType: 'User'
|
|
32
|
+
},
|
|
33
|
+
showPassword: false,
|
|
34
|
+
showconfirmPassword: false,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
initialValues = {
|
|
39
|
+
firstName: '',
|
|
40
|
+
lastName: '',
|
|
41
|
+
email: '',
|
|
42
|
+
password: '',
|
|
43
|
+
confirmPassword: '',
|
|
44
|
+
acceptTerms: false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
validationSchema = Yup.object().shape({
|
|
48
|
+
firstName: Yup.string()
|
|
49
|
+
.required("First name is required"),
|
|
50
|
+
lastName: Yup.string()
|
|
51
|
+
.required("Last name is required"),
|
|
52
|
+
email: Yup.string()
|
|
53
|
+
.email("Email is invalid")
|
|
54
|
+
.required("Email is required"),
|
|
55
|
+
password: Yup.string()
|
|
56
|
+
.required("No password provided.")
|
|
57
|
+
.min(8, "Password is too short - should be 8 characters minimum."),
|
|
58
|
+
confirmPassword: Yup.string()
|
|
59
|
+
.required("No password provided.")
|
|
60
|
+
.min(8, "Password is too short - should be 8 characters minimum.")
|
|
61
|
+
.oneOf([Yup.ref('password'), null], "Password did not match"),
|
|
62
|
+
acceptTerms: Yup.boolean()
|
|
63
|
+
.required("Accept terms is required to be checked")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
handleSignup = () => {
|
|
67
|
+
if (this.state.form.password === this.state.form.confirmPassword) {
|
|
68
|
+
if(!this.state.form.acceptTerms){
|
|
69
|
+
toast.info('Please accept terms')
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
let data = {...this.state.form, Username: this.state.form.email}
|
|
73
|
+
|
|
74
|
+
this.setState({ loading: true })
|
|
75
|
+
|
|
76
|
+
axios({
|
|
77
|
+
method: 'post',
|
|
78
|
+
url: `${API_BASE_URL}/avatar/register`,
|
|
79
|
+
data: data,
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
.then((response) => {
|
|
85
|
+
this.setState({loading: false})
|
|
86
|
+
|
|
87
|
+
if (response.data.result?.isError) {
|
|
88
|
+
toast.error(response.data.result.message);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toast.success(response.data.result.message);
|
|
93
|
+
this.props.hide();
|
|
94
|
+
this.props.showLogin();
|
|
95
|
+
})
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
this.setState({loading: false})
|
|
98
|
+
return { error: true, data: err };
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
toast.error("Password did not match")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
render() {
|
|
106
|
+
const { showPassword, showconfirmPassword, loading } = this.state;
|
|
107
|
+
const { show, hide, change } = this.props;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<Formik
|
|
112
|
+
initialValues={this.initialValues}
|
|
113
|
+
validationSchema={this.validationSchema}
|
|
114
|
+
onSubmit={(values, { setSubmitting, resetForm }) => {
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
let form = {...values, avatarType: 'User'};
|
|
117
|
+
this.setState({ form })
|
|
118
|
+
this.handleSignup();
|
|
119
|
+
|
|
120
|
+
setSubmitting(true);
|
|
121
|
+
// resetForm();
|
|
122
|
+
setSubmitting(false);
|
|
123
|
+
}, 400)
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{({ values, errors, touched, isSubmitting, handleChange, handleBlur, handleSubmit }) => (
|
|
127
|
+
<Modal centered className="custom-modal" show={show} onHide={hide}>
|
|
128
|
+
<Modal.Body>
|
|
129
|
+
<span className="form-cross-icon" onClick={hide}>
|
|
130
|
+
<i className="fa fa-times"></i>
|
|
131
|
+
</span>
|
|
132
|
+
|
|
133
|
+
<form className="custom-form" onSubmit={handleSubmit}>
|
|
134
|
+
<div className="form-header">
|
|
135
|
+
<h2>Sign Up</h2>
|
|
136
|
+
|
|
137
|
+
<p>
|
|
138
|
+
Already have an account?
|
|
139
|
+
<span className="text-link" onClick={change}> Beam In!</span>
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="form-inputs grid-form">
|
|
144
|
+
<div className={this.handleFormFieldClass(errors.firstName, touched.firstName)}>
|
|
145
|
+
<label>First Name</label>
|
|
146
|
+
<input
|
|
147
|
+
type="text"
|
|
148
|
+
name="firstName"
|
|
149
|
+
disabled={loading}
|
|
150
|
+
value={values.firstName}
|
|
151
|
+
onChange={handleChange}
|
|
152
|
+
onBlur={handleBlur}
|
|
153
|
+
placeholder="Jhone Doe"
|
|
154
|
+
/>
|
|
155
|
+
<span className="text-danger">{errors.firstName && touched.firstName && errors.firstName}</span>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className={this.handleFormFieldClass(errors.lastName, touched.lastName)}>
|
|
159
|
+
<label>Last Name</label>
|
|
160
|
+
<input
|
|
161
|
+
type="text"
|
|
162
|
+
name="lastName"
|
|
163
|
+
disabled={loading}
|
|
164
|
+
value={values.lastName}
|
|
165
|
+
onChange={handleChange}
|
|
166
|
+
onBlur={handleBlur}
|
|
167
|
+
placeholder="Jhone Doe"
|
|
168
|
+
/>
|
|
169
|
+
<span className="text-danger">{errors.lastName && touched.lastName && errors.lastName}</span>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div className={`${this.handleFormFieldClass(errors.email, touched.email)} mail-box`}>
|
|
173
|
+
<label>Email</label>
|
|
174
|
+
<input
|
|
175
|
+
type="email"
|
|
176
|
+
name="email"
|
|
177
|
+
disabled={loading}
|
|
178
|
+
value={values.email}
|
|
179
|
+
onChange={handleChange}
|
|
180
|
+
onBlur={handleBlur}
|
|
181
|
+
placeholder="name@example.com"
|
|
182
|
+
/>
|
|
183
|
+
<span className="text-danger">{errors.email && touched.email && errors.email}</span>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className={this.handleFormFieldClass(errors.password, touched.password)}>
|
|
187
|
+
<label>Password</label>
|
|
188
|
+
<div className="have-icon">
|
|
189
|
+
<input
|
|
190
|
+
type={`${showPassword ? "text" : "password"}`}
|
|
191
|
+
name="password"
|
|
192
|
+
value={values.password}
|
|
193
|
+
onChange={handleChange}
|
|
194
|
+
disabled={loading}
|
|
195
|
+
onBlur={handleBlur}
|
|
196
|
+
placeholder="password"
|
|
197
|
+
/>
|
|
198
|
+
<img
|
|
199
|
+
className="field-icon"
|
|
200
|
+
onClick={() => this.setState({ showPassword: !showPassword })}
|
|
201
|
+
src={showPassword ? ShowIcon : HideIcon}
|
|
202
|
+
alt="icon"
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
<span className="text-danger">{errors.password && touched.password && errors.password}</span>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className={this.handleFormFieldClass(errors.confirmPassword, touched.confirmPassword)} >
|
|
209
|
+
<label>Confirm Password</label>
|
|
210
|
+
<div className="have-icon">
|
|
211
|
+
<input
|
|
212
|
+
type={`${showconfirmPassword ? "text" : "password"}`}
|
|
213
|
+
name="confirmPassword"
|
|
214
|
+
value={values.confirmPassword}
|
|
215
|
+
onChange={handleChange}
|
|
216
|
+
onBlur={handleBlur}
|
|
217
|
+
disabled={loading}
|
|
218
|
+
placeholder="confirm password"
|
|
219
|
+
/>
|
|
220
|
+
<img
|
|
221
|
+
className="field-icon"
|
|
222
|
+
onClick={() => this.setState({ showconfirmPassword: !showconfirmPassword })}
|
|
223
|
+
src={showconfirmPassword ? ShowIcon : HideIcon}
|
|
224
|
+
alt="loading..."
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
<span className="text-danger">{errors.confirmPassword && touched.confirmPassword && errors.confirmPassword}</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="remember-me">
|
|
230
|
+
<label>
|
|
231
|
+
<input
|
|
232
|
+
type="checkbox"
|
|
233
|
+
name="acceptTerms"
|
|
234
|
+
value={values.acceptTerms}
|
|
235
|
+
onChange={handleChange}
|
|
236
|
+
disabled={loading}
|
|
237
|
+
id="acceptTerms"
|
|
238
|
+
/>
|
|
239
|
+
Accept Terms
|
|
240
|
+
</label>
|
|
241
|
+
</div>
|
|
242
|
+
<span className="text-danger">{errors.acceptTerms && touched.acceptTerms && errors.acceptTerms}</span>
|
|
243
|
+
|
|
244
|
+
{loading ? (
|
|
245
|
+
<button
|
|
246
|
+
type="submit"
|
|
247
|
+
className="submit-button"
|
|
248
|
+
disabled={true}
|
|
249
|
+
style={{
|
|
250
|
+
display: "flex",
|
|
251
|
+
justifyContent: "space-around",
|
|
252
|
+
cursor: "progress",
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
Submitting
|
|
256
|
+
<Loader
|
|
257
|
+
type="Oval"
|
|
258
|
+
height={15}
|
|
259
|
+
width={15}
|
|
260
|
+
color="#fff"
|
|
261
|
+
/>
|
|
262
|
+
</button>
|
|
263
|
+
) : (
|
|
264
|
+
<button
|
|
265
|
+
type="submit"
|
|
266
|
+
className="submit-button"
|
|
267
|
+
disabled={false}
|
|
268
|
+
>Submit</button>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
</form>
|
|
272
|
+
</Modal.Body>
|
|
273
|
+
</Modal>
|
|
274
|
+
)}
|
|
275
|
+
</Formik>
|
|
276
|
+
</>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
handleFormFieldClass(error, touched) {
|
|
281
|
+
let classes = "single-form-field ";
|
|
282
|
+
classes += (error && touched) ? "has-error" : "";
|
|
283
|
+
|
|
284
|
+
return classes;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { Component } from 'react';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
// import { ToastContainer, toast } from "react-toastify";
|
|
5
|
+
import { toast } from "react-toastify";
|
|
6
|
+
import API_BASE_URL from "../config/api";
|
|
7
|
+
|
|
8
|
+
// import { Link, withRouter } from "react-router-dom";
|
|
9
|
+
import { withRouter } from "react-router-dom";
|
|
10
|
+
|
|
11
|
+
class VerifyEmail extends Component {
|
|
12
|
+
|
|
13
|
+
constructor(props) {
|
|
14
|
+
super(props);
|
|
15
|
+
this.state = {
|
|
16
|
+
loading: true,
|
|
17
|
+
error: null,
|
|
18
|
+
message: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
componentDidMount() {
|
|
23
|
+
const searchParams = new URLSearchParams(this.props.location.search);
|
|
24
|
+
const token = searchParams.get('token');
|
|
25
|
+
|
|
26
|
+
axios.get(`${API_BASE_URL}/avatar/verify-email?token=${token}`)
|
|
27
|
+
.then(response => {
|
|
28
|
+
this.setState({loading: false})
|
|
29
|
+
|
|
30
|
+
if(response.data.result?.isError) {
|
|
31
|
+
toast.error(response?.data?.result.message);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.props.history.push('/');
|
|
36
|
+
|
|
37
|
+
toast.success(response?.data?.result.message);
|
|
38
|
+
this.setState({
|
|
39
|
+
message: response.data.message,
|
|
40
|
+
});
|
|
41
|
+
})
|
|
42
|
+
.catch(error => {
|
|
43
|
+
this.setState({
|
|
44
|
+
loading: false,
|
|
45
|
+
error: error.response.data.message,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render() {
|
|
51
|
+
const { loading, error, message } = this.state;
|
|
52
|
+
|
|
53
|
+
if (loading) {
|
|
54
|
+
return <div>Loading...</div>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (error) {
|
|
58
|
+
return <div>{error}</div>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
{message}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default withRouter(VerifyEmail);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useState, useCallback } from 'react';
|
|
3
|
+
import OasisModal from './OasisModal';
|
|
4
|
+
|
|
5
|
+
const API_URL = 'https://api.web4.oasisomniverse.one';
|
|
6
|
+
const SESSION_KEY = 'oasis_session';
|
|
7
|
+
|
|
8
|
+
function loadSession() {
|
|
9
|
+
try { return JSON.parse(sessionStorage.getItem(SESSION_KEY)); } catch { return null; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function AvatarConnect({ onLogin, onLogout, sessionKey = SESSION_KEY, apiUrl = API_URL }) {
|
|
13
|
+
const [session, setSession] = useState(loadSession);
|
|
14
|
+
const [open, setOpen] = useState(false);
|
|
15
|
+
const [username, setUsername] = useState('');
|
|
16
|
+
const [password, setPassword] = useState('');
|
|
17
|
+
const [showPwd, setShowPwd] = useState(false);
|
|
18
|
+
const [loading, setLoading] = useState(false);
|
|
19
|
+
const [error, setError] = useState('');
|
|
20
|
+
|
|
21
|
+
const doLogin = useCallback(async () => {
|
|
22
|
+
if (!username || !password) { setError('Please fill in all fields.'); return; }
|
|
23
|
+
setLoading(true); setError('');
|
|
24
|
+
try {
|
|
25
|
+
const { OASISClient } = await import('@oasisomniverse/web4-api');
|
|
26
|
+
const oasis = new OASISClient({ baseUrl: apiUrl });
|
|
27
|
+
const result = await oasis.auth.login({ username, password });
|
|
28
|
+
const karma = await oasis.karma.getKarmaForAvatar({ avatarId: result.avatarId });
|
|
29
|
+
const sess = { avatarId: result.avatarId, username, karma: karma.total ?? 0 };
|
|
30
|
+
sessionStorage.setItem(sessionKey, JSON.stringify(sess));
|
|
31
|
+
setSession(sess); setOpen(false);
|
|
32
|
+
onLogin?.(sess);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
setError(e?.message ?? 'Login failed. Please check your credentials.');
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
}, [username, password, apiUrl, sessionKey, onLogin]);
|
|
39
|
+
|
|
40
|
+
const doLogout = useCallback(() => {
|
|
41
|
+
sessionStorage.removeItem(sessionKey);
|
|
42
|
+
setSession(null);
|
|
43
|
+
onLogout?.();
|
|
44
|
+
}, [sessionKey, onLogout]);
|
|
45
|
+
|
|
46
|
+
if (session) {
|
|
47
|
+
return (
|
|
48
|
+
<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 }}>
|
|
49
|
+
<span style={{ fontSize:18 }}>👤</span>
|
|
50
|
+
<span style={{ color:'#fff', fontWeight:600 }}>{session.username}</span>
|
|
51
|
+
<span style={{ color:'#00c8ff', fontFamily:"'Orbitron',sans-serif", fontSize:11 }}>⚡ {session.karma ?? 0}</span>
|
|
52
|
+
<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>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<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' }}>
|
|
60
|
+
Beam In
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
<OasisModal open={open} onClose={() => setOpen(false)} title="Beam In">
|
|
64
|
+
<div style={{ display:'flex', flexDirection:'column', gap:16 }}>
|
|
65
|
+
{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>}
|
|
66
|
+
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
67
|
+
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Username</label>
|
|
68
|
+
<input value={username} onChange={e => setUsername(e.target.value)} type="text" placeholder="yourusername" autoComplete="username"
|
|
69
|
+
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' }} />
|
|
70
|
+
</div>
|
|
71
|
+
<div style={{ display:'flex', flexDirection:'column', gap:6 }}>
|
|
72
|
+
<label style={{ fontSize:11, fontWeight:600, letterSpacing:'.06em', color:'#7a9bbf', textTransform:'uppercase' }}>Password</label>
|
|
73
|
+
<div style={{ position:'relative' }}>
|
|
74
|
+
<input value={password} onChange={e => setPassword(e.target.value)} type={showPwd ? 'text' : 'password'} placeholder="••••••••" autoComplete="current-password"
|
|
75
|
+
onKeyDown={e => e.key === 'Enter' && doLogin()}
|
|
76
|
+
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' }} />
|
|
77
|
+
<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 }}>
|
|
78
|
+
{showPwd ? '🙈' : '👁️'}
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<button onClick={doLogin} disabled={loading}
|
|
83
|
+
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 }}>
|
|
84
|
+
{loading ? 'Connecting…' : 'Beam In'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</OasisModal>
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const s = {
|
|
5
|
+
shell: { display: 'flex', flexDirection: 'column', gap: 18 },
|
|
6
|
+
header: { textAlign: 'center' },
|
|
7
|
+
title: { fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' },
|
|
8
|
+
sub: { fontSize: 13, color: '#7a9bbf', margin: 0 },
|
|
9
|
+
error: { background: 'rgba(255,80,80,.12)', border: '1px solid rgba(255,80,80,.3)', color: '#ff6b6b', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
10
|
+
success: { background: 'rgba(72,220,130,.12)', border: '1px solid rgba(72,220,130,.3)', color: '#48dc82', borderRadius: 8, padding: '10px 14px', fontSize: 13 },
|
|
11
|
+
grid2: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 },
|
|
12
|
+
field: { display: 'flex', flexDirection: 'column', gap: 6 },
|
|
13
|
+
label: { fontSize: 12, fontWeight: 600, letterSpacing: '.06em', color: '#7a9bbf', textTransform: 'uppercase' },
|
|
14
|
+
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' },
|
|
15
|
+
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 },
|
|
16
|
+
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' },
|
|
17
|
+
btnDisabled: { opacity: .5, cursor: 'not-allowed' },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function Contact() {
|
|
21
|
+
const [fields, setFields] = useState({ firstName: '', lastName: '', email: '', subject: '', message: '' });
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const [error, setError] = useState('');
|
|
24
|
+
const [sent, setSent] = useState(false);
|
|
25
|
+
|
|
26
|
+
function set(k) { return e => setFields(f => ({ ...f, [k]: e.target.value })); }
|
|
27
|
+
|
|
28
|
+
async function submit() {
|
|
29
|
+
if (!fields.email || !fields.message) { setError('Please fill in your email and message.'); return; }
|
|
30
|
+
setLoading(true); setError('');
|
|
31
|
+
try {
|
|
32
|
+
await fetch('https://formsubmit.co/ajax/davidellams@hotmail.com', {
|
|
33
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
34
|
+
body: JSON.stringify(fields),
|
|
35
|
+
});
|
|
36
|
+
setSent(true);
|
|
37
|
+
} catch (e) { setError(e?.message ?? 'Send failed. Please try again.'); }
|
|
38
|
+
finally { setLoading(false); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div style={s.shell}>
|
|
43
|
+
<div style={s.header}><h2 style={s.title}>Contact Us</h2><p style={s.sub}>Get in touch with the OASIS team.</p></div>
|
|
44
|
+
{sent ? (
|
|
45
|
+
<div style={s.success}>✅ Message sent! We'll be in touch soon.</div>
|
|
46
|
+
) : (
|
|
47
|
+
<>
|
|
48
|
+
{error && <div style={s.error}>{error}</div>}
|
|
49
|
+
<div style={s.grid2}>
|
|
50
|
+
<div style={s.field}><label style={s.label}>First Name</label><input style={s.input} value={fields.firstName} onChange={set('firstName')} placeholder="John" /></div>
|
|
51
|
+
<div style={s.field}><label style={s.label}>Last Name</label><input style={s.input} value={fields.lastName} onChange={set('lastName')} placeholder="Doe" /></div>
|
|
52
|
+
</div>
|
|
53
|
+
<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>
|
|
54
|
+
<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>
|
|
55
|
+
<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>
|
|
56
|
+
<button style={{ ...s.btn, ...(loading ? s.btnDisabled : {}) }} disabled={loading} onClick={submit}>{loading ? 'Sending…' : 'Send Message'}</button>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const RARITY_COLORS = { Common: '#7a9bbf', Rare: '#5ba8ff', Epic: '#b87fff', Legendary: '#ffb43c' };
|
|
5
|
+
const INITIAL = [
|
|
6
|
+
{ id: '1', name: 'Cosmic Egg', rarity: 'Legendary', hatchProgress: 75, hatched: false, reward: '+500 Karma Companion' },
|
|
7
|
+
{ id: '2', name: 'Forest Egg', rarity: 'Common', hatchProgress: 100, hatched: true, reward: 'Woodland Sprite' },
|
|
8
|
+
{ id: '3', name: 'Astral Egg', rarity: 'Epic', hatchProgress: 30, hatched: false, reward: '+200 Karma Companion' },
|
|
9
|
+
{ id: '4', name: 'Ocean Egg', rarity: 'Rare', hatchProgress: 55, hatched: false, reward: 'Sea Guardian' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function Eggs() {
|
|
13
|
+
const [eggs, setEggs] = useState(INITIAL);
|
|
14
|
+
function incubate(id) {
|
|
15
|
+
setEggs(es => es.map(e => {
|
|
16
|
+
if (e.id !== id) return e;
|
|
17
|
+
const next = Math.min(100, e.hatchProgress + 25);
|
|
18
|
+
return { ...e, hatchProgress: next, hatched: next >= 100 };
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
23
|
+
<div style={{ textAlign: 'center' }}>
|
|
24
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🥚 Eggs</h2>
|
|
25
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Hatch eggs to discover rare OASIS companions and rewards.</p>
|
|
26
|
+
</div>
|
|
27
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(160px,1fr))', gap: 16 }}>
|
|
28
|
+
{eggs.map(e => (
|
|
29
|
+
<div key={e.id} style={{ background: e.hatched ? 'rgba(255,180,60,.04)' : 'rgba(255,255,255,.04)', border: `1px solid ${e.hatched ? 'rgba(255,180,60,.3)' : 'rgba(0,200,255,.15)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
|
30
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '3px 10px', border: `1px solid ${RARITY_COLORS[e.rarity]}44`, color: RARITY_COLORS[e.rarity] }}>{e.rarity}</div>
|
|
31
|
+
<div style={{ fontSize: 40 }}>{e.hatched ? '🐣' : '🥚'}</div>
|
|
32
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#fff', textAlign: 'center' }}>{e.name}</div>
|
|
33
|
+
{!e.hatched ? (
|
|
34
|
+
<>
|
|
35
|
+
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }}>
|
|
36
|
+
<div style={{ width: '100%', height: 6, background: 'rgba(255,255,255,.1)', borderRadius: 999, overflow: 'hidden' }}>
|
|
37
|
+
<div style={{ height: '100%', width: `${e.hatchProgress}%`, background: 'linear-gradient(90deg,#00c8ff,#0080ff)', borderRadius: 999, transition: 'width .4s' }} />
|
|
38
|
+
</div>
|
|
39
|
+
<div style={{ fontSize: 11, color: '#7a9bbf' }}>{e.hatchProgress}%</div>
|
|
40
|
+
</div>
|
|
41
|
+
<button style={{ width: '100%', background: 'linear-gradient(135deg,#ffb43c,#ff8c00)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, letterSpacing: '.08em', padding: 8, cursor: 'pointer' }} onClick={() => incubate(e.id)}>Incubate</button>
|
|
42
|
+
</>
|
|
43
|
+
) : (
|
|
44
|
+
<div style={{ fontSize: 12, color: '#ffb43c', fontWeight: 600, textAlign: 'center' }}>✨ {e.reward}</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const INITIAL = [
|
|
5
|
+
{ id: '1', name: 'Karma Wars', genre: 'Strategy', description: 'Battle other avatars for karma dominance in real-time.', players: 12450, karma: 50, icon: '⚔️', playing: false },
|
|
6
|
+
{ id: '2', name: 'Seed Garden', genre: 'Simulation', description: 'Grow and cultivate a virtual garden to earn seeds and karma.', players: 8900, karma: 30, icon: '🌱', playing: false },
|
|
7
|
+
{ id: '3', name: 'OASIS Racer', genre: 'Racing', description: 'Race across virtual worlds and earn speed karma.', players: 5600, karma: 40, icon: '🏎️', playing: false },
|
|
8
|
+
{ id: '4', name: 'NFT Quest', genre: 'RPG', description: 'An epic RPG adventure to discover legendary NFTs.', players: 22000, karma: 80, icon: '🗡️', playing: false },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export function Game() {
|
|
12
|
+
const [games, setGames] = useState(INITIAL);
|
|
13
|
+
function toggle(id) { setGames(l => l.map(x => x.id === id ? { ...x, playing: !x.playing } : x)); }
|
|
14
|
+
return (
|
|
15
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
16
|
+
<div style={{ textAlign: 'center' }}>
|
|
17
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🎮 Games</h2>
|
|
18
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Play games in the OASIS to earn karma and rewards.</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(200px,1fr))', gap: 16 }}>
|
|
21
|
+
{games.map(g => (
|
|
22
|
+
<div key={g.id} style={{ background: g.playing ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${g.playing ? 'rgba(0,200,255,.5)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: '18px 14px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
23
|
+
<div style={{ fontSize: 40 }}>{g.icon}</div>
|
|
24
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: '#5ba8ff' }}>{g.genre}</div>
|
|
25
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff' }}>{g.name}</div>
|
|
26
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5, flex: 1 }}>{g.description}</div>
|
|
27
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
28
|
+
<div style={{ fontSize: 11, color: '#7a9bbf' }}>👥 {g.players.toLocaleString()}</div>
|
|
29
|
+
<div style={{ fontSize: 11, color: '#48dc82', fontWeight: 600 }}>+{g.karma} karma/hr</div>
|
|
30
|
+
</div>
|
|
31
|
+
<button onClick={() => toggle(g.id)} style={{ background: g.playing ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: g.playing ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: g.playing ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: 9, cursor: 'pointer', marginTop: 4 }}>{g.playing ? '⏹ Stop' : '▶ Play'}</button>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const METRICS = [{ label: 'Throughput', value: '4.2 TB/s' }, { label: 'Latency', value: '0.3ms' }, { label: 'Uptime', value: '99.98%' }];
|
|
5
|
+
const NODES = [
|
|
6
|
+
{ id: '1', name: 'ONODE Alpha — US East', status: 'active', latency: 12 },
|
|
7
|
+
{ id: '2', name: 'ONODE Beta — EU West', status: 'active', latency: 28 },
|
|
8
|
+
{ id: '3', name: 'ONODE Gamma — Asia Pacific', status: 'idle', latency: 55 },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export function HyperDrive() {
|
|
12
|
+
const [active, setActive] = useState(false);
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
15
|
+
<div style={{ textAlign: 'center' }}>
|
|
16
|
+
<h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🚀 HyperDrive</h2>
|
|
17
|
+
<p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Quantum-speed data layer for the OASIS Omniverse.</p>
|
|
18
|
+
</div>
|
|
19
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 14, background: active ? 'rgba(0,200,255,.05)' : 'rgba(255,255,255,.04)', border: `1px solid ${active ? 'rgba(0,200,255,.4)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16 }}>
|
|
20
|
+
<div style={{ fontSize: 28 }}>{active ? '⚡' : '💤'}</div>
|
|
21
|
+
<div style={{ flex: 1 }}>
|
|
22
|
+
<div style={{ fontSize: 12, color: '#7a9bbf', textTransform: 'uppercase', letterSpacing: '.06em' }}>HyperDrive Status</div>
|
|
23
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 16, color: active ? '#00c8ff' : '#7a9bbf', marginTop: 2 }}>{active ? 'ACTIVE' : 'OFFLINE'}</div>
|
|
24
|
+
</div>
|
|
25
|
+
<button onClick={() => setActive(a => !a)} style={{ background: active ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: active ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: active ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '10px 20px', cursor: 'pointer' }}>{active ? 'Disengage' : 'Engage'}</button>
|
|
26
|
+
</div>
|
|
27
|
+
{active && (
|
|
28
|
+
<>
|
|
29
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
|
|
30
|
+
{METRICS.map(m => (
|
|
31
|
+
<div key={m.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 14, textAlign: 'center' }}>
|
|
32
|
+
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 20, color: '#00c8ff', fontWeight: 600 }}>{m.value}</div>
|
|
33
|
+
<div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{m.label}</div>
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Connected Nodes</div>
|
|
38
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
39
|
+
{NODES.map(n => (
|
|
40
|
+
<div key={n.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'rgba(255,255,255,.03)', border: '1px solid rgba(0,200,255,.07)', borderRadius: 8 }}>
|
|
41
|
+
<div style={{ width: 8, height: 8, borderRadius: '50%', background: n.status === 'active' ? '#48dc82' : '#7a9bbf', boxShadow: n.status === 'active' ? '0 0 6px #48dc82' : undefined, flexShrink: 0 }} />
|
|
42
|
+
<div style={{ flex: 1, fontSize: 13, color: '#e0f0ff' }}>{n.name}</div>
|
|
43
|
+
<div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 12, color: '#7a9bbf' }}>{n.latency}ms</div>
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
</>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|