@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.
@@ -1,50 +1,50 @@
1
- import { useState } from 'react';
2
-
3
- const STATS = [{ label: 'Peers', value: '2,847' }, { label: 'Speed', value: '1.2 Gb/s' }, { label: 'Encrypted', value: '100%' }];
4
- const PEERS = [
5
- { id: '1', name: 'AvatarX_7291', location: 'New York, US', ping: 14 },
6
- { id: '2', name: 'StarKeeper_99', location: 'London, UK', ping: 32 },
7
- { id: '3', name: 'NeonRider_404', location: 'Tokyo, JP', ping: 67 },
8
- ];
9
-
10
- export function ONET() {
11
- const [connected, setConnected] = useState(false);
12
- return (
13
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
14
- <div style={{ textAlign: 'center' }}>
15
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🌐 ONET</h2>
16
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>The OASIS decentralised internet layer.</p>
17
- </div>
18
- <div style={{ display: 'flex', alignItems: 'center', gap: 14, background: 'rgba(255,255,255,.04)', border: `1px solid ${connected ? 'rgba(72,220,130,.3)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16 }}>
19
- <div style={{ width: 12, height: 12, borderRadius: '50%', background: connected ? '#48dc82' : '#4a6a88', boxShadow: connected ? '0 0 10px #48dc82' : undefined, flexShrink: 0, transition: 'all .3s' }} />
20
- <div style={{ flex: 1 }}>
21
- <div style={{ fontSize: 12, color: '#7a9bbf', textTransform: 'uppercase', letterSpacing: '.06em' }}>ONET Connection</div>
22
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 14, color: '#fff', marginTop: 3 }}>{connected ? 'Connected' : 'Disconnected'}</div>
23
- </div>
24
- <button onClick={() => setConnected(c => !c)} 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' }}>{connected ? 'Disconnect' : 'Connect'}</button>
25
- </div>
26
- {connected && (
27
- <>
28
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
29
- {STATS.map(s => (
30
- <div key={s.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 14, textAlign: 'center' }}>
31
- <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 18, color: '#00c8ff', fontWeight: 600 }}>{s.value}</div>
32
- <div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{s.label}</div>
33
- </div>
34
- ))}
35
- </div>
36
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Active Peers</div>
37
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
38
- {PEERS.map(p => (
39
- <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', background: 'rgba(255,255,255,.03)', border: '1px solid rgba(0,200,255,.07)', borderRadius: 8 }}>
40
- <div style={{ fontSize: 20 }}>👤</div>
41
- <div style={{ flex: 1 }}><div style={{ fontSize: 13, color: '#e0f0ff' }}>{p.name}</div><div style={{ fontSize: 11, color: '#4a6a88', marginTop: 2 }}>{p.location}</div></div>
42
- <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 12, color: '#48dc82' }}>{p.ping}ms</div>
43
- </div>
44
- ))}
45
- </div>
46
- </>
47
- )}
48
- </div>
49
- );
50
- }
1
+ import { useState } from 'react';
2
+
3
+ const STATS = [{ label: 'Peers', value: '2,847' }, { label: 'Speed', value: '1.2 Gb/s' }, { label: 'Encrypted', value: '100%' }];
4
+ const PEERS = [
5
+ { id: '1', name: 'AvatarX_7291', location: 'New York, US', ping: 14 },
6
+ { id: '2', name: 'StarKeeper_99', location: 'London, UK', ping: 32 },
7
+ { id: '3', name: 'NeonRider_404', location: 'Tokyo, JP', ping: 67 },
8
+ ];
9
+
10
+ export function ONET() {
11
+ const [connected, setConnected] = useState(false);
12
+ return (
13
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
14
+ <div style={{ textAlign: 'center' }}>
15
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🌐 ONET</h2>
16
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>The OASIS decentralised internet layer.</p>
17
+ </div>
18
+ <div style={{ display: 'flex', alignItems: 'center', gap: 14, background: 'rgba(255,255,255,.04)', border: `1px solid ${connected ? 'rgba(72,220,130,.3)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16 }}>
19
+ <div style={{ width: 12, height: 12, borderRadius: '50%', background: connected ? '#48dc82' : '#4a6a88', boxShadow: connected ? '0 0 10px #48dc82' : undefined, flexShrink: 0, transition: 'all .3s' }} />
20
+ <div style={{ flex: 1 }}>
21
+ <div style={{ fontSize: 12, color: '#7a9bbf', textTransform: 'uppercase', letterSpacing: '.06em' }}>ONET Connection</div>
22
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 14, color: '#fff', marginTop: 3 }}>{connected ? 'Connected' : 'Disconnected'}</div>
23
+ </div>
24
+ <button onClick={() => setConnected(c => !c)} 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' }}>{connected ? 'Disconnect' : 'Connect'}</button>
25
+ </div>
26
+ {connected && (
27
+ <>
28
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
29
+ {STATS.map(s => (
30
+ <div key={s.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 14, textAlign: 'center' }}>
31
+ <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 18, color: '#00c8ff', fontWeight: 600 }}>{s.value}</div>
32
+ <div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{s.label}</div>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Active Peers</div>
37
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
38
+ {PEERS.map(p => (
39
+ <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', background: 'rgba(255,255,255,.03)', border: '1px solid rgba(0,200,255,.07)', borderRadius: 8 }}>
40
+ <div style={{ fontSize: 20 }}>👤</div>
41
+ <div style={{ flex: 1 }}><div style={{ fontSize: 13, color: '#e0f0ff' }}>{p.name}</div><div style={{ fontSize: 11, color: '#4a6a88', marginTop: 2 }}>{p.location}</div></div>
42
+ <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 12, color: '#48dc82' }}>{p.ping}ms</div>
43
+ </div>
44
+ ))}
45
+ </div>
46
+ </>
47
+ )}
48
+ </div>
49
+ );
50
+ }
@@ -1,49 +1,49 @@
1
- import { useState } from 'react';
2
-
3
- const METRICS = [{ label: 'CPU', value: '18%' }, { label: 'RAM', value: '2.1 GB' }, { label: 'Storage', value: '47 GB' }, { label: 'Blocks', value: '12,450' }];
4
- const LOGS = [
5
- { time: '09:42:01', level: 'info', message: 'ONODE started. Connecting to ONET…' },
6
- { time: '09:42:02', level: 'info', message: 'Connected to 12 peers.' },
7
- { time: '09:42:05', level: 'info', message: 'Block #12450 validated.' },
8
- { time: '09:42:08', level: 'warn', message: 'Peer ONODE-77 latency high: 340ms.' },
9
- { time: '09:42:12', level: 'info', message: 'Data sync complete. 100% coverage.' },
10
- ];
11
- const LOG_COLORS = { info: '#00c8ff', warn: '#ffb43c', error: '#ff6b6b' };
12
-
13
- export function ONODE() {
14
- const [running, setRunning] = useState(false);
15
- return (
16
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
- <div style={{ textAlign: 'center' }}>
18
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🖥️ ONODE</h2>
19
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Your personal OASIS node. Store, validate, and relay data.</p>
20
- </div>
21
- <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
22
- <div style={{ flex: 1, background: 'rgba(255,255,255,.04)', border: `1px solid ${running ? 'rgba(72,220,130,.3)' : 'rgba(255,80,80,.2)'}`, borderRadius: 8, padding: '10px 14px', fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: running ? '#48dc82' : '#ff6b6b' }}>{running ? '● Running' : '○ Stopped'}</div>
23
- <button onClick={() => setRunning(r => !r)} style={{ background: running ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: running ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: running ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '10px 20px', cursor: 'pointer' }}>{running ? 'Stop Node' : 'Start Node'}</button>
24
- </div>
25
- {running && (
26
- <>
27
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
28
- {METRICS.map(m => (
29
- <div key={m.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 12, textAlign: 'center' }}>
30
- <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 16, color: '#00c8ff', fontWeight: 600 }}>{m.value}</div>
31
- <div style={{ fontSize: 10, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{m.label}</div>
32
- </div>
33
- ))}
34
- </div>
35
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Node Logs</div>
36
- <div style={{ background: '#020a14', border: '1px solid rgba(0,200,255,.1)', borderRadius: 8, padding: 12, display: 'flex', flexDirection: 'column', gap: 5, maxHeight: 160, overflowY: 'auto', fontFamily: "'Share Tech Mono',monospace" }}>
37
- {LOGS.map(l => (
38
- <div key={l.time} style={{ display: 'flex', gap: 10, fontSize: 11 }}>
39
- <span style={{ color: '#4a6a88', flexShrink: 0 }}>{l.time}</span>
40
- <span style={{ color: LOG_COLORS[l.level] }}>[{l.level.toUpperCase()}]</span>
41
- <span style={{ color: '#7a9bbf' }}>{l.message}</span>
42
- </div>
43
- ))}
44
- </div>
45
- </>
46
- )}
47
- </div>
48
- );
49
- }
1
+ import { useState } from 'react';
2
+
3
+ const METRICS = [{ label: 'CPU', value: '18%' }, { label: 'RAM', value: '2.1 GB' }, { label: 'Storage', value: '47 GB' }, { label: 'Blocks', value: '12,450' }];
4
+ const LOGS = [
5
+ { time: '09:42:01', level: 'info', message: 'ONODE started. Connecting to ONET…' },
6
+ { time: '09:42:02', level: 'info', message: 'Connected to 12 peers.' },
7
+ { time: '09:42:05', level: 'info', message: 'Block #12450 validated.' },
8
+ { time: '09:42:08', level: 'warn', message: 'Peer ONODE-77 latency high: 340ms.' },
9
+ { time: '09:42:12', level: 'info', message: 'Data sync complete. 100% coverage.' },
10
+ ];
11
+ const LOG_COLORS = { info: '#00c8ff', warn: '#ffb43c', error: '#ff6b6b' };
12
+
13
+ export function ONODE() {
14
+ const [running, setRunning] = useState(false);
15
+ return (
16
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
+ <div style={{ textAlign: 'center' }}>
18
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>🖥️ ONODE</h2>
19
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Your personal OASIS node. Store, validate, and relay data.</p>
20
+ </div>
21
+ <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
22
+ <div style={{ flex: 1, background: 'rgba(255,255,255,.04)', border: `1px solid ${running ? 'rgba(72,220,130,.3)' : 'rgba(255,80,80,.2)'}`, borderRadius: 8, padding: '10px 14px', fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: running ? '#48dc82' : '#ff6b6b' }}>{running ? '● Running' : '○ Stopped'}</div>
23
+ <button onClick={() => setRunning(r => !r)} style={{ background: running ? 'rgba(255,80,80,.2)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: running ? '1px solid rgba(255,80,80,.35)' : 'none', borderRadius: 8, color: running ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: '.08em', padding: '10px 20px', cursor: 'pointer' }}>{running ? 'Stop Node' : 'Start Node'}</button>
24
+ </div>
25
+ {running && (
26
+ <>
27
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
28
+ {METRICS.map(m => (
29
+ <div key={m.label} style={{ background: 'rgba(255,255,255,.04)', border: '1px solid rgba(0,200,255,.1)', borderRadius: 10, padding: 12, textAlign: 'center' }}>
30
+ <div style={{ fontFamily: "'Share Tech Mono',monospace", fontSize: 16, color: '#00c8ff', fontWeight: 600 }}>{m.value}</div>
31
+ <div style={{ fontSize: 10, color: '#7a9bbf', marginTop: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>{m.label}</div>
32
+ </div>
33
+ ))}
34
+ </div>
35
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, color: '#7a9bbf', letterSpacing: '.06em', textTransform: 'uppercase' }}>Node Logs</div>
36
+ <div style={{ background: '#020a14', border: '1px solid rgba(0,200,255,.1)', borderRadius: 8, padding: 12, display: 'flex', flexDirection: 'column', gap: 5, maxHeight: 160, overflowY: 'auto', fontFamily: "'Share Tech Mono',monospace" }}>
37
+ {LOGS.map(l => (
38
+ <div key={l.time} style={{ display: 'flex', gap: 10, fontSize: 11 }}>
39
+ <span style={{ color: '#4a6a88', flexShrink: 0 }}>{l.time}</span>
40
+ <span style={{ color: LOG_COLORS[l.level] }}>[{l.level.toUpperCase()}]</span>
41
+ <span style={{ color: '#7a9bbf' }}>{l.message}</span>
42
+ </div>
43
+ ))}
44
+ </div>
45
+ </>
46
+ )}
47
+ </div>
48
+ );
49
+ }
@@ -1,9 +1,9 @@
1
- .oasis-modal-backdrop{position:fixed;inset:0;background:rgba(3,7,20,.75);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:9999;animation:oasisFadeIn .25s ease}
2
- .oasis-modal-box{background:#0d1829;border:1px solid rgba(0,200,255,.18);border-radius:16px;padding:28px;width:min(520px,92vw);max-height:88vh;overflow-y:auto;animation:oasisSlideUp .25s ease}
3
- .oasis-modal-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:14px;border-bottom:1px solid;margin-bottom:20px}
4
- .oasis-modal-title{font-family:'Orbitron',sans-serif;font-size:15px;font-weight:700;letter-spacing:.06em}
5
- .oasis-modal-close{background:none;border:none;color:#7a9bbf;font-size:18px;cursor:pointer;padding:0;line-height:1}
6
- .oasis-modal-close:hover{color:#fff}
7
- .oasis-modal-body{color:#a8bfd8;font-size:14px;line-height:1.6}
8
- @keyframes oasisFadeIn{from{opacity:0}to{opacity:1}}
9
- @keyframes oasisSlideUp{from{transform:translateY(16px);opacity:0}to{transform:none;opacity:1}}
1
+ .oasis-modal-backdrop{position:fixed;inset:0;background:rgba(3,7,20,.75);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:9999;animation:oasisFadeIn .25s ease}
2
+ .oasis-modal-box{background:#0d1829;border:1px solid rgba(0,200,255,.18);border-radius:16px;padding:28px;width:min(520px,92vw);max-height:88vh;overflow-y:auto;animation:oasisSlideUp .25s ease}
3
+ .oasis-modal-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:14px;border-bottom:1px solid;margin-bottom:20px}
4
+ .oasis-modal-title{font-family:'Orbitron',sans-serif;font-size:15px;font-weight:700;letter-spacing:.06em}
5
+ .oasis-modal-close{background:none;border:none;color:#7a9bbf;font-size:18px;cursor:pointer;padding:0;line-height:1}
6
+ .oasis-modal-close:hover{color:#fff}
7
+ .oasis-modal-body{color:#a8bfd8;font-size:14px;line-height:1.6}
8
+ @keyframes oasisFadeIn{from{opacity:0}to{opacity:1}}
9
+ @keyframes oasisSlideUp{from{transform:translateY(16px);opacity:0}to{transform:none;opacity:1}}
@@ -1,29 +1,29 @@
1
- import React, { useEffect } from 'react';
2
- import './OasisModal.css';
3
-
4
- export default function OasisModal({ open, onClose, title, accentColor = '#00c8ff', children }) {
5
- useEffect(() => {
6
- if (!open) return;
7
- const onKey = e => { if (e.key === 'Escape') onClose?.(); };
8
- document.addEventListener('keydown', onKey);
9
- document.body.style.overflow = 'hidden';
10
- return () => {
11
- document.removeEventListener('keydown', onKey);
12
- document.body.style.overflow = '';
13
- };
14
- }, [open, onClose]);
15
-
16
- if (!open) return null;
17
-
18
- return (
19
- <div className="oasis-modal-backdrop" onClick={e => { if (e.target === e.currentTarget) onClose?.(); }}>
20
- <div className="oasis-modal-box" role="dialog" aria-modal="true">
21
- <div className="oasis-modal-header" style={{ borderBottomColor: accentColor + '33' }}>
22
- <span className="oasis-modal-title" style={{ color: accentColor }}>{title}</span>
23
- <button className="oasis-modal-close" onClick={onClose} aria-label="Close">&#x2715;</button>
24
- </div>
25
- <div className="oasis-modal-body">{children}</div>
26
- </div>
27
- </div>
28
- );
29
- }
1
+ import React, { useEffect } from 'react';
2
+ import './OasisModal.css';
3
+
4
+ export default function OasisModal({ open, onClose, title, accentColor = '#00c8ff', children }) {
5
+ useEffect(() => {
6
+ if (!open) return;
7
+ const onKey = e => { if (e.key === 'Escape') onClose?.(); };
8
+ document.addEventListener('keydown', onKey);
9
+ document.body.style.overflow = 'hidden';
10
+ return () => {
11
+ document.removeEventListener('keydown', onKey);
12
+ document.body.style.overflow = '';
13
+ };
14
+ }, [open, onClose]);
15
+
16
+ if (!open) return null;
17
+
18
+ return (
19
+ <div className="oasis-modal-backdrop" onClick={e => { if (e.target === e.currentTarget) onClose?.(); }}>
20
+ <div className="oasis-modal-box" role="dialog" aria-modal="true">
21
+ <div className="oasis-modal-header" style={{ borderBottomColor: accentColor + '33' }}>
22
+ <span className="oasis-modal-title" style={{ color: accentColor }}>{title}</span>
23
+ <button className="oasis-modal-close" onClick={onClose} aria-label="Close">&#x2715;</button>
24
+ </div>
25
+ <div className="oasis-modal-body">{children}</div>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -1,61 +1,61 @@
1
- import { useState, useEffect, useRef } from 'react';
2
-
3
- const PROVIDERS = [
4
- { id: 'ethereum', name: 'Ethereum', icon: '⟠', color: '#627eea' },
5
- { id: 'solana', name: 'Solana', icon: '◎', color: '#9945ff' },
6
- { id: 'polygon', name: 'Polygon', icon: '⬡', color: '#8247e5' },
7
- { id: 'arbitrum', name: 'Arbitrum', icon: '🔵', color: '#2d9cdb' },
8
- { id: 'eosio', name: 'EOSIO', icon: '🔮', color: '#443f54' },
9
- { id: 'holochain', name: 'Holochain', icon: '⬡', color: '#00e5be' },
10
- { id: 'thegraph', name: 'The Graph', icon: '📊', color: '#6f4cff' },
11
- ];
12
-
13
- /**
14
- * ProviderDropdown — blockchain provider selector.
15
- * Props:
16
- * defaultProvider string — id of default provider
17
- * onChange fn — called with selected provider object
18
- */
19
- export function ProviderDropdown({ defaultProvider = 'ethereum', onChange }) {
20
- const [selected, setSelected] = useState(() => PROVIDERS.find(p => p.id === defaultProvider) || PROVIDERS[0]);
21
- const [open, setOpen] = useState(false);
22
- const ref = useRef(null);
23
-
24
- useEffect(() => {
25
- function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
26
- document.addEventListener('mousedown', onDoc);
27
- return () => document.removeEventListener('mousedown', onDoc);
28
- }, []);
29
-
30
- function select(p) { setSelected(p); setOpen(false); onChange?.(p); }
31
-
32
- return (
33
- <div ref={ref} style={{ position: 'relative', fontFamily: "'Rajdhani',sans-serif" }}>
34
- <div
35
- onClick={() => setOpen(o => !o)}
36
- style={{ display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,.05)', border: `1px solid ${open ? 'rgba(0,200,255,.5)' : 'rgba(0,200,255,.25)'}`, borderRadius: 10, padding: '10px 14px', cursor: 'pointer', color: '#fff', transition: 'border-color .2s' }}
37
- >
38
- <span style={{ fontSize: 20, width: 28, textAlign: 'center', color: selected.color }}>{selected.icon}</span>
39
- <div style={{ flex: 1 }}>
40
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, fontWeight: 700 }}>{selected.name}</div>
41
- <div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 2 }}>Active Provider</div>
42
- </div>
43
- <span style={{ fontSize: 10, color: '#7a9bbf', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s', display: 'inline-block' }}>▼</span>
44
- </div>
45
- {open && (
46
- <div style={{ position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0, background: '#0d1b2e', border: '1px solid rgba(0,200,255,.2)', borderRadius: 10, overflow: 'hidden', zIndex: 999, boxShadow: '0 8px 32px rgba(0,0,0,.5)' }}>
47
- {PROVIDERS.map(p => (
48
- <div
49
- key={p.id}
50
- onClick={() => select(p)}
51
- style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', cursor: 'pointer', color: '#e0f0ff', background: p.id === selected.id ? 'rgba(0,200,255,.12)' : 'transparent', borderLeft: p.id === selected.id ? '3px solid #00c8ff' : '3px solid transparent', transition: 'background .15s' }}
52
- >
53
- <span style={{ fontSize: 18, width: 24, textAlign: 'center', color: p.color }}>{p.icon}</span>
54
- <span style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700 }}>{p.name}</span>
55
- </div>
56
- ))}
57
- </div>
58
- )}
59
- </div>
60
- );
61
- }
1
+ import { useState, useEffect, useRef } from 'react';
2
+
3
+ const PROVIDERS = [
4
+ { id: 'ethereum', name: 'Ethereum', icon: '⟠', color: '#627eea' },
5
+ { id: 'solana', name: 'Solana', icon: '◎', color: '#9945ff' },
6
+ { id: 'polygon', name: 'Polygon', icon: '⬡', color: '#8247e5' },
7
+ { id: 'arbitrum', name: 'Arbitrum', icon: '🔵', color: '#2d9cdb' },
8
+ { id: 'eosio', name: 'EOSIO', icon: '🔮', color: '#443f54' },
9
+ { id: 'holochain', name: 'Holochain', icon: '⬡', color: '#00e5be' },
10
+ { id: 'thegraph', name: 'The Graph', icon: '📊', color: '#6f4cff' },
11
+ ];
12
+
13
+ /**
14
+ * ProviderDropdown — blockchain provider selector.
15
+ * Props:
16
+ * defaultProvider string — id of default provider
17
+ * onChange fn — called with selected provider object
18
+ */
19
+ export function ProviderDropdown({ defaultProvider = 'ethereum', onChange }) {
20
+ const [selected, setSelected] = useState(() => PROVIDERS.find(p => p.id === defaultProvider) || PROVIDERS[0]);
21
+ const [open, setOpen] = useState(false);
22
+ const ref = useRef(null);
23
+
24
+ useEffect(() => {
25
+ function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
26
+ document.addEventListener('mousedown', onDoc);
27
+ return () => document.removeEventListener('mousedown', onDoc);
28
+ }, []);
29
+
30
+ function select(p) { setSelected(p); setOpen(false); onChange?.(p); }
31
+
32
+ return (
33
+ <div ref={ref} style={{ position: 'relative', fontFamily: "'Rajdhani',sans-serif" }}>
34
+ <div
35
+ onClick={() => setOpen(o => !o)}
36
+ style={{ display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,.05)', border: `1px solid ${open ? 'rgba(0,200,255,.5)' : 'rgba(0,200,255,.25)'}`, borderRadius: 10, padding: '10px 14px', cursor: 'pointer', color: '#fff', transition: 'border-color .2s' }}
37
+ >
38
+ <span style={{ fontSize: 20, width: 28, textAlign: 'center', color: selected.color }}>{selected.icon}</span>
39
+ <div style={{ flex: 1 }}>
40
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, fontWeight: 700 }}>{selected.name}</div>
41
+ <div style={{ fontSize: 11, color: '#7a9bbf', marginTop: 2 }}>Active Provider</div>
42
+ </div>
43
+ <span style={{ fontSize: 10, color: '#7a9bbf', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s', display: 'inline-block' }}>▼</span>
44
+ </div>
45
+ {open && (
46
+ <div style={{ position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0, background: '#0d1b2e', border: '1px solid rgba(0,200,255,.2)', borderRadius: 10, overflow: 'hidden', zIndex: 999, boxShadow: '0 8px 32px rgba(0,0,0,.5)' }}>
47
+ {PROVIDERS.map(p => (
48
+ <div
49
+ key={p.id}
50
+ onClick={() => select(p)}
51
+ style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', cursor: 'pointer', color: '#e0f0ff', background: p.id === selected.id ? 'rgba(0,200,255,.12)' : 'transparent', borderLeft: p.id === selected.id ? '3px solid #00c8ff' : '3px solid transparent', transition: 'background .15s' }}
52
+ >
53
+ <span style={{ fontSize: 18, width: 24, textAlign: 'center', color: p.color }}>{p.icon}</span>
54
+ <span style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 12, fontWeight: 700 }}>{p.name}</span>
55
+ </div>
56
+ ))}
57
+ </div>
58
+ )}
59
+ </div>
60
+ );
61
+ }
@@ -1,38 +1,38 @@
1
- import { useState } from 'react';
2
-
3
- const INITIAL = [
4
- { id: '1', name: 'Ethereum', icon: '⟠', color: '#627eea', type: 'Blockchain', description: 'Connect your Ethereum wallet (MetaMask, WalletConnect).', connected: false },
5
- { id: '2', name: 'Solana', icon: '◎', color: '#9945ff', type: 'Blockchain', description: 'Connect your Solana wallet (Phantom, Solflare).', connected: false },
6
- { id: '3', name: 'EOSIO', icon: '🔮', color: '#443f54', type: 'Blockchain', description: 'Connect via EOSIO for high-speed transactions.', connected: false },
7
- { id: '4', name: 'Holochain', icon: '⬡', color: '#00e5be', type: 'P2P Network', description: 'Peer-to-peer data storage on Holochain.', connected: false },
8
- { id: '5', name: 'The Graph', icon: '📊', color: '#6f4cff', type: 'Indexer', description: 'Query blockchain data with The Graph protocol.', connected: false },
9
- ];
10
-
11
- export function Providers() {
12
- const [providers, setProviders] = useState(INITIAL);
13
- function toggle(id) { setProviders(l => l.map(x => x.id === id ? { ...x, connected: !x.connected } : 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' }}>🔗 Providers</h2>
18
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Connect blockchain providers to your OASIS avatar.</p>
19
- </div>
20
- <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
21
- {providers.map(p => (
22
- <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 14, background: p.connected ? 'rgba(72,220,130,.03)' : 'rgba(255,255,255,.04)', border: `1px solid ${p.connected ? 'rgba(72,220,130,.3)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: '14px 16px' }}>
23
- <div style={{ fontSize: 26, flexShrink: 0, color: p.color }}>{p.icon}</div>
24
- <div style={{ flex: 1 }}>
25
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 2 }}>{p.name}</div>
26
- <div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 4 }}>{p.type}</div>
27
- <div style={{ fontSize: 12, color: '#7a9bbf' }}>{p.description}</div>
28
- </div>
29
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, flexShrink: 0 }}>
30
- <div style={{ width: 8, height: 8, borderRadius: '50%', background: p.connected ? '#48dc82' : '#4a6a88', boxShadow: p.connected ? '0 0 6px #48dc82' : undefined }} />
31
- <button onClick={() => toggle(p.id)} style={{ background: p.connected ? 'rgba(255,80,80,.15)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: p.connected ? '1px solid rgba(255,80,80,.3)' : 'none', borderRadius: 7, color: p.connected ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 10, fontWeight: 700, letterSpacing: '.06em', padding: '7px 14px', cursor: 'pointer', whiteSpace: 'nowrap' }}>{p.connected ? 'Disconnect' : 'Connect'}</button>
32
- </div>
33
- </div>
34
- ))}
35
- </div>
36
- </div>
37
- );
38
- }
1
+ import { useState } from 'react';
2
+
3
+ const INITIAL = [
4
+ { id: '1', name: 'Ethereum', icon: '⟠', color: '#627eea', type: 'Blockchain', description: 'Connect your Ethereum wallet (MetaMask, WalletConnect).', connected: false },
5
+ { id: '2', name: 'Solana', icon: '◎', color: '#9945ff', type: 'Blockchain', description: 'Connect your Solana wallet (Phantom, Solflare).', connected: false },
6
+ { id: '3', name: 'EOSIO', icon: '🔮', color: '#443f54', type: 'Blockchain', description: 'Connect via EOSIO for high-speed transactions.', connected: false },
7
+ { id: '4', name: 'Holochain', icon: '⬡', color: '#00e5be', type: 'P2P Network', description: 'Peer-to-peer data storage on Holochain.', connected: false },
8
+ { id: '5', name: 'The Graph', icon: '📊', color: '#6f4cff', type: 'Indexer', description: 'Query blockchain data with The Graph protocol.', connected: false },
9
+ ];
10
+
11
+ export function Providers() {
12
+ const [providers, setProviders] = useState(INITIAL);
13
+ function toggle(id) { setProviders(l => l.map(x => x.id === id ? { ...x, connected: !x.connected } : 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' }}>🔗 Providers</h2>
18
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Connect blockchain providers to your OASIS avatar.</p>
19
+ </div>
20
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
21
+ {providers.map(p => (
22
+ <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 14, background: p.connected ? 'rgba(72,220,130,.03)' : 'rgba(255,255,255,.04)', border: `1px solid ${p.connected ? 'rgba(72,220,130,.3)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: '14px 16px' }}>
23
+ <div style={{ fontSize: 26, flexShrink: 0, color: p.color }}>{p.icon}</div>
24
+ <div style={{ flex: 1 }}>
25
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 2 }}>{p.name}</div>
26
+ <div style={{ fontSize: 11, color: '#5ba8ff', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 4 }}>{p.type}</div>
27
+ <div style={{ fontSize: 12, color: '#7a9bbf' }}>{p.description}</div>
28
+ </div>
29
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, flexShrink: 0 }}>
30
+ <div style={{ width: 8, height: 8, borderRadius: '50%', background: p.connected ? '#48dc82' : '#4a6a88', boxShadow: p.connected ? '0 0 6px #48dc82' : undefined }} />
31
+ <button onClick={() => toggle(p.id)} style={{ background: p.connected ? 'rgba(255,80,80,.15)' : 'linear-gradient(135deg,#00c8ff,#0080ff)', border: p.connected ? '1px solid rgba(255,80,80,.3)' : 'none', borderRadius: 7, color: p.connected ? '#ff6b6b' : '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 10, fontWeight: 700, letterSpacing: '.06em', padding: '7px 14px', cursor: 'pointer', whiteSpace: 'nowrap' }}>{p.connected ? 'Disconnect' : 'Connect'}</button>
32
+ </div>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -1,40 +1,40 @@
1
- import { useState } from 'react';
2
-
3
- const DIFF_COLORS = { Easy: '#48dc82', Medium: '#ffb43c', Hard: '#ff6b6b', Legendary: '#b87fff' };
4
- const INITIAL = [
5
- { id: '1', title: 'Avatar Awakening', description: 'Complete your avatar profile and connect your first provider.', reward: 200, difficulty: 'Easy', accepted: true, completed: true },
6
- { id: '2', title: 'Karma Seeker', description: 'Earn 1000 karma through good deeds and OASIS contributions.', reward: 500, difficulty: 'Medium', accepted: true, completed: false },
7
- { id: '3', title: 'The NFT Collector', description: 'Acquire 5 unique NFTs across different categories.', reward: 1000, difficulty: 'Hard', accepted: false, completed: false },
8
- { id: '4', title: 'Omniverse Explorer', description: 'Visit every major hub location on the OASIS map.', reward: 2500, difficulty: 'Legendary', accepted: false, completed: false },
9
- ];
10
-
11
- export function Quest() {
12
- const [quests, setQuests] = useState(INITIAL);
13
- function accept(id) { setQuests(l => l.map(x => x.id === id ? { ...x, accepted: true } : x)); }
14
- function complete(id) { setQuests(l => l.map(x => x.id === id ? { ...x, completed: true } : x)); }
15
- return (
16
- <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
- <div style={{ textAlign: 'center' }}>
18
- <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>⚔️ Quests</h2>
19
- <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Embark on quests to gain karma and rare rewards.</p>
20
- </div>
21
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
22
- {quests.map(q => (
23
- <div key={q.id} style={{ display: 'flex', gap: 14, alignItems: 'flex-start', background: 'rgba(255,255,255,.04)', border: `1px solid ${q.completed ? 'rgba(72,220,130,.2)' : q.accepted ? 'rgba(0,200,255,.4)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16, opacity: q.completed ? .6 : 1 }}>
24
- <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '4px 10px', border: `1px solid ${DIFF_COLORS[q.difficulty]}44`, color: DIFF_COLORS[q.difficulty], whiteSpace: 'nowrap', flexShrink: 0 }}>{q.difficulty}</div>
25
- <div style={{ flex: 1 }}>
26
- <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 4 }}>{q.title}</div>
27
- <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5, marginBottom: 6 }}>{q.description}</div>
28
- <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{q.reward} karma</div>
29
- </div>
30
- <div style={{ flexShrink: 0 }}>
31
- {q.completed ? <div style={{ fontSize: 13, color: '#48dc82', fontWeight: 600 }}>✅ Done</div>
32
- : !q.accepted ? <button onClick={() => accept(q.id)} style={{ background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '8px 16px', cursor: 'pointer' }}>Accept Quest</button>
33
- : <button onClick={() => complete(q.id)} style={{ background: 'linear-gradient(135deg,#48dc82,#00aa55)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '8px 16px', cursor: 'pointer' }}>Finish</button>}
34
- </div>
35
- </div>
36
- ))}
37
- </div>
38
- </div>
39
- );
40
- }
1
+ import { useState } from 'react';
2
+
3
+ const DIFF_COLORS = { Easy: '#48dc82', Medium: '#ffb43c', Hard: '#ff6b6b', Legendary: '#b87fff' };
4
+ const INITIAL = [
5
+ { id: '1', title: 'Avatar Awakening', description: 'Complete your avatar profile and connect your first provider.', reward: 200, difficulty: 'Easy', accepted: true, completed: true },
6
+ { id: '2', title: 'Karma Seeker', description: 'Earn 1000 karma through good deeds and OASIS contributions.', reward: 500, difficulty: 'Medium', accepted: true, completed: false },
7
+ { id: '3', title: 'The NFT Collector', description: 'Acquire 5 unique NFTs across different categories.', reward: 1000, difficulty: 'Hard', accepted: false, completed: false },
8
+ { id: '4', title: 'Omniverse Explorer', description: 'Visit every major hub location on the OASIS map.', reward: 2500, difficulty: 'Legendary', accepted: false, completed: false },
9
+ ];
10
+
11
+ export function Quest() {
12
+ const [quests, setQuests] = useState(INITIAL);
13
+ function accept(id) { setQuests(l => l.map(x => x.id === id ? { ...x, accepted: true } : x)); }
14
+ function complete(id) { setQuests(l => l.map(x => x.id === id ? { ...x, completed: true } : x)); }
15
+ return (
16
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
17
+ <div style={{ textAlign: 'center' }}>
18
+ <h2 style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 20, color: '#fff', margin: '0 0 6px' }}>⚔️ Quests</h2>
19
+ <p style={{ fontSize: 13, color: '#7a9bbf', margin: 0 }}>Embark on quests to gain karma and rare rewards.</p>
20
+ </div>
21
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
22
+ {quests.map(q => (
23
+ <div key={q.id} style={{ display: 'flex', gap: 14, alignItems: 'flex-start', background: 'rgba(255,255,255,.04)', border: `1px solid ${q.completed ? 'rgba(72,220,130,.2)' : q.accepted ? 'rgba(0,200,255,.4)' : 'rgba(0,200,255,.12)'}`, borderRadius: 12, padding: 16, opacity: q.completed ? .6 : 1 }}>
24
+ <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', borderRadius: 999, padding: '4px 10px', border: `1px solid ${DIFF_COLORS[q.difficulty]}44`, color: DIFF_COLORS[q.difficulty], whiteSpace: 'nowrap', flexShrink: 0 }}>{q.difficulty}</div>
25
+ <div style={{ flex: 1 }}>
26
+ <div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 13, color: '#fff', marginBottom: 4 }}>{q.title}</div>
27
+ <div style={{ fontSize: 12, color: '#7a9bbf', lineHeight: 1.5, marginBottom: 6 }}>{q.description}</div>
28
+ <div style={{ fontSize: 12, color: '#48dc82', fontWeight: 600 }}>+{q.reward} karma</div>
29
+ </div>
30
+ <div style={{ flexShrink: 0 }}>
31
+ {q.completed ? <div style={{ fontSize: 13, color: '#48dc82', fontWeight: 600 }}>✅ Done</div>
32
+ : !q.accepted ? <button onClick={() => accept(q.id)} style={{ background: 'linear-gradient(135deg,#00c8ff,#0080ff)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '8px 16px', cursor: 'pointer' }}>Accept Quest</button>
33
+ : <button onClick={() => complete(q.id)} style={{ background: 'linear-gradient(135deg,#48dc82,#00aa55)', border: 'none', borderRadius: 7, color: '#fff', fontFamily: "'Orbitron',sans-serif", fontSize: 11, fontWeight: 700, padding: '8px 16px', cursor: 'pointer' }}>Finish</button>}
34
+ </div>
35
+ </div>
36
+ ))}
37
+ </div>
38
+ </div>
39
+ );
40
+ }