@microcosmmoney/portal-react 3.6.0 → 3.8.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.
@@ -0,0 +1,4 @@
1
+ export interface MicrocosmEmailChangeCardProps {
2
+ onSuccess?: (newEmail: string) => void;
3
+ }
4
+ export declare function MicrocosmEmailChangeCard({ onSuccess }?: MicrocosmEmailChangeCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MicrocosmEmailChangeCard = MicrocosmEmailChangeCard;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const auth_react_1 = require("@microcosmmoney/auth-react");
8
+ const terminal_1 = require("../terminal");
9
+ function MicrocosmEmailChangeCard({ onSuccess } = {}) {
10
+ const { requestEmailChange, verifyEmailChange, loading, error, clearError } = (0, auth_react_1.useEmailVerification)();
11
+ const [step, setStep] = (0, react_1.useState)('idle');
12
+ const [newEmail, setNewEmail] = (0, react_1.useState)('');
13
+ const [password, setPassword] = (0, react_1.useState)('');
14
+ const [code, setCode] = (0, react_1.useState)('');
15
+ const handleRequest = async () => {
16
+ if (!newEmail || !password)
17
+ return;
18
+ clearError();
19
+ try {
20
+ await requestEmailChange(newEmail, password);
21
+ setStep('verify');
22
+ }
23
+ catch { }
24
+ };
25
+ const handleVerify = async () => {
26
+ if (code.length !== 6)
27
+ return;
28
+ clearError();
29
+ try {
30
+ await verifyEmailChange(newEmail, code);
31
+ setStep('idle');
32
+ const finalEmail = newEmail;
33
+ setNewEmail('');
34
+ setPassword('');
35
+ setCode('');
36
+ onSuccess?.(finalEmail);
37
+ }
38
+ catch { }
39
+ };
40
+ if (step === 'idle') {
41
+ return ((0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2 mb-3", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-neutral-300 font-medium tracking-wider", children: "CHANGE EMAIL" }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400 mb-3", children: "Update your email address. A verification code will be sent to the new email." }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setStep('form'), className: "px-3 py-1.5 border border-neutral-700 text-neutral-300 hover:bg-neutral-800 rounded text-xs", children: "Change Email" })] }));
42
+ }
43
+ return ((0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-neutral-300 font-medium tracking-wider", children: "CHANGE EMAIL" }) }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-2 bg-red-900/20 border border-red-800 rounded text-xs text-red-300", children: error })), step === 'form' && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "New email address", className: "w-full bg-neutral-800 border border-neutral-700 text-white p-2 rounded text-sm outline-none focus:border-cyan-500" }), (0, jsx_runtime_1.jsx)("input", { type: "password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "Current password", className: "w-full bg-neutral-800 border border-neutral-700 text-white p-2 rounded text-sm outline-none focus:border-cyan-500" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => {
44
+ setStep('idle');
45
+ setNewEmail('');
46
+ setPassword('');
47
+ clearError();
48
+ }, className: "flex-1 px-3 py-1.5 text-neutral-500 hover:text-neutral-300 text-xs", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleRequest, disabled: loading || !newEmail || !password, className: "flex-1 px-3 py-1.5 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-xs disabled:opacity-50", children: loading ? 'Sending...' : 'Send Code' })] })] })), step === 'verify' && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-neutral-400", children: ["Enter the 6-digit code sent to ", (0, jsx_runtime_1.jsx)("span", { className: "text-cyan-400", children: newEmail })] }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: code, onChange: (e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6)), placeholder: "000000", className: "w-full bg-neutral-800 border border-neutral-700 text-white p-2 rounded text-center text-lg font-mono tracking-[0.5em] outline-none focus:border-cyan-500", maxLength: 6 }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => { setStep('form'); setCode(''); clearError(); }, className: "flex-1 px-3 py-1.5 text-neutral-500 hover:text-neutral-300 text-xs", children: "Back" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleVerify, disabled: loading || code.length !== 6, className: "flex-1 px-3 py-1.5 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-xs disabled:opacity-50", children: loading ? 'Verifying...' : 'Verify' })] })] }))] }) }));
49
+ }
@@ -1,6 +1,7 @@
1
+ import { type ReactNode } from 'react';
1
2
  export interface MicrocosmProfilePageProps {
2
3
  basePath?: string;
3
4
  onNavigate?: (path: string) => void;
4
- mainPortalSettingsUrl?: string;
5
+ walletSection?: ReactNode;
5
6
  }
6
- export declare function MicrocosmProfilePage({ mainPortalSettingsUrl, }?: MicrocosmProfilePageProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function MicrocosmProfilePage({ walletSection, }?: MicrocosmProfilePageProps): import("react/jsx-runtime").JSX.Element;
@@ -6,6 +6,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("react");
7
7
  const auth_react_1 = require("@microcosmmoney/auth-react");
8
8
  const terminal_1 = require("../terminal");
9
+ const email_change_card_1 = require("./email-change-card");
10
+ const two_factor_settings_1 = require("./two-factor-settings");
9
11
  const API_BASE = 'https://api.microcosm.money/v1';
10
12
  const LEVEL_INFO = {
11
13
  miner: { label: 'Miner', description: 'Registered user', color: 'text-white' },
@@ -14,7 +16,7 @@ const LEVEL_INFO = {
14
16
  warden: { label: 'Warden', description: 'Auto-minted Sector NFT (≥10 Matrix)', color: 'text-cyan-400' },
15
17
  admiral: { label: 'Admiral', description: 'Auto-minted System NFT (≥10 Sector)', color: 'text-red-400' },
16
18
  };
17
- function MicrocosmProfilePage({ mainPortalSettingsUrl = '/user-system/profile', } = {}) {
19
+ function MicrocosmProfilePage({ walletSection, } = {}) {
18
20
  const api = (0, auth_react_1.useMicrocosmApi)();
19
21
  const { getAccessToken } = (0, auth_react_1.useMicrocosmContext)();
20
22
  const authState = (0, auth_react_1.useAuth)();
@@ -27,6 +29,8 @@ function MicrocosmProfilePage({ mainPortalSettingsUrl = '/user-system/profile',
27
29
  const [saving, setSaving] = (0, react_1.useState)(false);
28
30
  const [uploadingAvatar, setUploadingAvatar] = (0, react_1.useState)(false);
29
31
  const [error, setError] = (0, react_1.useState)(null);
32
+ const [resendingVerification, setResendingVerification] = (0, react_1.useState)(false);
33
+ const [resendMessage, setResendMessage] = (0, react_1.useState)(null);
30
34
  const fileInputRef = (0, react_1.useRef)(null);
31
35
  const loadProfile = (0, react_1.useCallback)(async () => {
32
36
  setLoading(true);
@@ -130,5 +134,20 @@ function MicrocosmProfilePage({ mainPortalSettingsUrl = '/user-system/profile',
130
134
  return ((0, jsx_runtime_1.jsxs)("div", { className: "max-w-7xl mx-auto px-3 py-4 space-y-3 xs:px-4 xs:space-y-4 sm:px-6 sm:py-6 sm:space-y-6 font-mono", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-lg sm:text-2xl font-bold text-white tracking-wider", children: "Profile" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs sm:text-sm text-neutral-400", children: "Your account information" })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-3 bg-red-900/20 border border-red-800 rounded text-sm text-red-300", children: error })), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-4 mb-6 pb-6 border-b border-neutral-700", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative group", children: [profile?.avatar_url ? ((0, jsx_runtime_1.jsx)("img", { src: profile.avatar_url, alt: "", className: "h-20 w-20 rounded bg-neutral-800 border border-neutral-700 object-cover" })) : ((0, jsx_runtime_1.jsx)("div", { className: "h-20 w-20 rounded bg-cyan-400/20 border border-cyan-400/30 flex items-center justify-center text-cyan-400 text-3xl font-bold", children: displayName.charAt(0).toUpperCase() })), (0, jsx_runtime_1.jsx)("button", { onClick: () => fileInputRef.current?.click(), disabled: uploadingAvatar, className: "absolute inset-0 bg-black/50 rounded opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-white text-xs disabled:opacity-30", children: uploadingAvatar ? '...' : 'edit' }), (0, jsx_runtime_1.jsx)("input", { ref: fileInputRef, type: "file", accept: "image/jpeg,image/png,image/gif,image/webp", onChange: handleAvatarChange, className: "hidden" })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1", children: editing ? ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-3", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: editName, onChange: (e) => setEditName(e.target.value), className: "w-full bg-neutral-800 border border-neutral-600 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-cyan-400", placeholder: "Display name" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: handleSave, disabled: saving, className: "px-3 py-1.5 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: saving ? 'Saving...' : 'Save' }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
131
135
  setEditName(profile?.display_name || '');
132
136
  setEditing(false);
133
- }, disabled: saving, className: "px-3 py-1.5 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-lg text-white", children: displayName }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setEditing(true), className: "text-xs text-neutral-500 hover:text-cyan-400", children: "[edit]" })] }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-neutral-400 mt-1", children: profile?.email || '(no email)' }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2 mt-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-neutral-800 text-neutral-300 rounded text-xs border border-neutral-700", children: profile?.role || 'user' }), profile?.email_verified ? ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-green-900/30 text-green-400 rounded text-xs border border-green-800", children: "verified" })) : ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-red-900/30 text-red-400 rounded text-xs border border-red-800", children: "unverified" }))] })] })) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "UID" }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-white font-mono break-all", children: profile?.uid || userInfo?.uid || '-' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Short ID" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white font-mono", children: profile?.short_id || '-' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Created" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white", children: formatDate(profile?.created_at) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Last Login" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white", children: formatDate(profile?.last_login_at) })] })] })] }), levelData && ((0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { title: "Level Status", children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between p-4 bg-neutral-800 rounded", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "CURRENT LEVEL" }), (0, jsx_runtime_1.jsx)("div", { className: `text-xl font-bold ${lvlInfo.color}`, children: lvlInfo.label }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500 mt-1", children: lvlInfo.description })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-right", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "HOLDINGS" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-white font-mono", children: ["S:", levelData.holdings?.station ?? 0, " M:", levelData.holdings?.matrix ?? 0, " Se:", levelData.holdings?.sector ?? 0, " Sy:", levelData.holdings?.system ?? 0] })] })] }), levelData.next_level_requirement && ((0, jsx_runtime_1.jsxs)("div", { className: "p-4 bg-neutral-800 rounded", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-neutral-400 tracking-wider", children: "PROGRESS" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-white text-sm font-mono", children: [levelData.progress_percent ?? 0, "%"] })] }), (0, jsx_runtime_1.jsx)("div", { className: "w-full bg-neutral-900 rounded-full h-2", children: (0, jsx_runtime_1.jsx)("div", { className: "h-2 rounded-full bg-cyan-400 transition-all", style: { width: `${Math.min(levelData.progress_percent ?? 0, 100)}%` } }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500 mt-2", children: levelData.next_level_requirement.description || '' }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-neutral-400 mt-1", children: [levelData.next_level_requirement.have, "/", levelData.next_level_requirement.need, ' ', levelData.next_level_requirement.tier, " \u2192 ", ' ', (0, jsx_runtime_1.jsx)("span", { className: "text-white", children: levelData.next_rank })] })] }))] }) })), (0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { title: "Advanced Settings", children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-3 text-sm text-neutral-400", children: [(0, jsx_runtime_1.jsx)("p", { children: "Email verification, email change, two-factor authentication, and wallet management are available on the main portal:" }), (0, jsx_runtime_1.jsx)("a", { href: mainPortalSettingsUrl, className: "inline-block px-3 py-1.5 border border-cyan-800 text-cyan-400 hover:bg-cyan-950 rounded text-sm", children: "Open main portal settings \u2192" })] }) }), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-neutral-400 text-sm mb-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-cyan-400", children: "!" }), " Security"] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1 text-sm text-neutral-400", children: [(0, jsx_runtime_1.jsx)("p", { children: "- Keep your account credentials secure" }), (0, jsx_runtime_1.jsx)("p", { children: "- Change password periodically via main portal" }), (0, jsx_runtime_1.jsx)("p", { children: "- Report suspicious activity immediately" }), (0, jsx_runtime_1.jsx)("p", { children: "- Verify your email to receive important notifications" })] })] })] }));
137
+ }, disabled: saving, className: "px-3 py-1.5 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-lg text-white", children: displayName }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setEditing(true), className: "text-xs text-neutral-500 hover:text-cyan-400", children: "[edit]" })] }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-neutral-400 mt-1", children: profile?.email || '(no email)' }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2 mt-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-neutral-800 text-neutral-300 rounded text-xs border border-neutral-700", children: profile?.role || 'user' }), profile?.email_verified ? ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-green-900/30 text-green-400 rounded text-xs border border-green-800", children: "verified" })) : ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 bg-red-900/30 text-red-400 rounded text-xs border border-red-800", children: "unverified" }))] })] })) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "UID" }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-white font-mono break-all", children: profile?.uid || userInfo?.uid || '-' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Short ID" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white font-mono", children: profile?.short_id || '-' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Created" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white", children: formatDate(profile?.created_at) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "Last Login" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white", children: formatDate(profile?.last_login_at) })] })] })] }), levelData && ((0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { title: "Level Status", children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between p-4 bg-neutral-800 rounded", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "CURRENT LEVEL" }), (0, jsx_runtime_1.jsx)("div", { className: `text-xl font-bold ${lvlInfo.color}`, children: lvlInfo.label }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500 mt-1", children: lvlInfo.description })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-right", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "HOLDINGS" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-white font-mono", children: ["S:", levelData.holdings?.station ?? 0, " M:", levelData.holdings?.matrix ?? 0, " Se:", levelData.holdings?.sector ?? 0, " Sy:", levelData.holdings?.system ?? 0] })] })] }), levelData.next_level_requirement && ((0, jsx_runtime_1.jsxs)("div", { className: "p-4 bg-neutral-800 rounded", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-neutral-400 tracking-wider", children: "PROGRESS" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-white text-sm font-mono", children: [levelData.progress_percent ?? 0, "%"] })] }), (0, jsx_runtime_1.jsx)("div", { className: "w-full bg-neutral-900 rounded-full h-2", children: (0, jsx_runtime_1.jsx)("div", { className: "h-2 rounded-full bg-cyan-400 transition-all", style: { width: `${Math.min(levelData.progress_percent ?? 0, 100)}%` } }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500 mt-2", children: levelData.next_level_requirement.description || '' }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-neutral-400 mt-1", children: [levelData.next_level_requirement.have, "/", levelData.next_level_requirement.need, ' ', levelData.next_level_requirement.tier, " \u2192 ", ' ', (0, jsx_runtime_1.jsx)("span", { className: "text-white", children: levelData.next_rank })] })] }))] }) })), (0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { title: "Email Verification", children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "STATUS" }), profile?.email_verified ? ((0, jsx_runtime_1.jsx)("div", { className: "text-green-400 text-sm", children: "Verified" })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-red-400 text-sm", children: "Unverified" }))] }), !profile?.email_verified && ((0, jsx_runtime_1.jsx)("button", { onClick: async () => {
138
+ setResendingVerification(true);
139
+ setResendMessage(null);
140
+ try {
141
+ await api.post('/users/me/resend-verification', {});
142
+ setResendMessage('Verification email sent — check your inbox');
143
+ }
144
+ catch (e) {
145
+ setResendMessage(e instanceof Error ? e.message : 'Failed to send');
146
+ }
147
+ finally {
148
+ setResendingVerification(false);
149
+ }
150
+ }, disabled: resendingVerification, className: "px-3 py-1.5 border border-cyan-800 text-cyan-400 hover:bg-cyan-950 rounded text-xs disabled:opacity-50", children: resendingVerification ? 'Sending...' : 'Resend Email' }))] }), resendMessage && ((0, jsx_runtime_1.jsx)("div", { className: "text-xs text-cyan-400", children: resendMessage }))] }) }), (0, jsx_runtime_1.jsx)(email_change_card_1.MicrocosmEmailChangeCard, { onSuccess: (newEmail) => {
151
+ setProfile(prev => (prev ? { ...prev, email: newEmail, email_verified: true } : prev));
152
+ } }), (0, jsx_runtime_1.jsx)(two_factor_settings_1.MicrocosmTwoFactorSettings, {}), walletSection, (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-neutral-400 text-sm mb-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-cyan-400", children: "!" }), " Security"] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-1 text-sm text-neutral-400", children: [(0, jsx_runtime_1.jsx)("p", { children: "- Keep your account credentials secure" }), (0, jsx_runtime_1.jsx)("p", { children: "- Change password periodically via main portal" }), (0, jsx_runtime_1.jsx)("p", { children: "- Report suspicious activity immediately" }), (0, jsx_runtime_1.jsx)("p", { children: "- Verify your email to receive important notifications" })] })] })] }));
134
153
  }
@@ -0,0 +1,3 @@
1
+ export interface MicrocosmTwoFactorSettingsProps {
2
+ }
3
+ export declare function MicrocosmTwoFactorSettings({}?: MicrocosmTwoFactorSettingsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MicrocosmTwoFactorSettings = MicrocosmTwoFactorSettings;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const auth_react_1 = require("@microcosmmoney/auth-react");
8
+ const terminal_1 = require("../terminal");
9
+ function MicrocosmTwoFactorSettings({} = {}) {
10
+ const { status, setupData, loading, error, beginSetup, verifySetup, disable, clearError, clearSetup, } = (0, auth_react_1.useTwoFactor)();
11
+ const [step, setStep] = (0, react_1.useState)('idle');
12
+ const [verifyCode, setVerifyCode] = (0, react_1.useState)('');
13
+ const [disablePassword, setDisablePassword] = (0, react_1.useState)('');
14
+ const [showSecret, setShowSecret] = (0, react_1.useState)(false);
15
+ const [backupCodes, setBackupCodes] = (0, react_1.useState)(null);
16
+ const handleBegin = async () => {
17
+ clearError();
18
+ try {
19
+ await beginSetup();
20
+ setStep('qr');
21
+ }
22
+ catch { }
23
+ };
24
+ const handleVerify = async () => {
25
+ if (verifyCode.length !== 6)
26
+ return;
27
+ clearError();
28
+ try {
29
+ const codes = await verifySetup(verifyCode);
30
+ setVerifyCode('');
31
+ if (codes && codes.length > 0) {
32
+ setBackupCodes(codes);
33
+ setStep('backup');
34
+ }
35
+ else {
36
+ setStep('idle');
37
+ }
38
+ }
39
+ catch { }
40
+ };
41
+ const handleDisable = async () => {
42
+ if (!disablePassword)
43
+ return;
44
+ clearError();
45
+ try {
46
+ await disable(disablePassword);
47
+ setDisablePassword('');
48
+ setStep('idle');
49
+ }
50
+ catch { }
51
+ };
52
+ const copyBackupCodes = async () => {
53
+ if (!backupCodes)
54
+ return;
55
+ try {
56
+ await navigator.clipboard.writeText(backupCodes.join('\n'));
57
+ }
58
+ catch { }
59
+ };
60
+ if (loading && !status)
61
+ return null;
62
+ return ((0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-neutral-300 font-medium tracking-wider", children: "TWO FACTOR AUTH" }) }), (0, jsx_runtime_1.jsx)("span", { className: `px-2 py-0.5 rounded text-xs ${status?.enabled ? 'bg-green-900/30 text-green-400 border border-green-800' : 'bg-neutral-800 text-neutral-400 border border-neutral-700'}`, children: status?.enabled ? 'enabled' : 'disabled' })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-2 bg-red-900/20 border border-red-800 rounded text-xs text-red-300 mb-3", children: error })), step === 'idle' && !status?.enabled && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-3", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Add an extra layer of security using Google Authenticator or any TOTP app." }), (0, jsx_runtime_1.jsx)("button", { onClick: handleBegin, disabled: loading, className: "px-3 py-1.5 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-xs disabled:opacity-50", children: loading ? 'Setting up...' : 'Enable 2FA' })] })), step === 'idle' && status?.enabled && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 mb-1", children: "STATUS" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-green-400 text-sm", children: ["Active ", status.created_at ? `since ${new Date(status.created_at).toLocaleDateString()}` : ''] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setStep('disable'), className: "px-3 py-1.5 border border-red-900 text-red-400 hover:bg-red-950 rounded text-xs", children: "Disable 2FA" })] })), step === 'qr' && setupData && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "1. Scan this QR code with Google Authenticator or any TOTP app." }), setupData.qr_code && ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center bg-white rounded p-4", children: (0, jsx_runtime_1.jsx)("img", { src: setupData.qr_code.startsWith('data:') ? setupData.qr_code : `data:image/png;base64,${setupData.qr_code}`, alt: "2FA QR Code", className: "w-48 h-48" }) })), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-800 rounded p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs text-neutral-400", children: "MANUAL KEY" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowSecret(!showSecret), className: "text-neutral-500 hover:text-neutral-300 text-xs", children: showSecret ? 'hide' : 'show' })] }), (0, jsx_runtime_1.jsx)("code", { className: "text-xs text-cyan-400 break-all", children: showSecret ? setupData.secret : '••••••••••••••••' })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "2. Enter the 6-digit code from your app." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: verifyCode, onChange: (e) => setVerifyCode(e.target.value.replace(/\D/g, '').slice(0, 6)), placeholder: "000000", className: "flex-1 bg-neutral-800 border border-neutral-700 text-white p-2 rounded text-center text-lg font-mono tracking-[0.5em] outline-none focus:border-cyan-500", maxLength: 6 }), (0, jsx_runtime_1.jsx)("button", { onClick: handleVerify, disabled: loading || verifyCode.length !== 6, className: "px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-xs disabled:opacity-50", children: loading ? 'Verifying...' : 'Verify' })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
63
+ setStep('idle');
64
+ setVerifyCode('');
65
+ clearSetup();
66
+ }, className: "w-full py-2 text-neutral-500 hover:text-neutral-300 text-xs", children: "Cancel" })] })), step === 'backup' && backupCodes && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-yellow-950/30 border border-yellow-900/50 rounded p-3", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs text-yellow-400 font-medium mb-1", children: "Save your backup codes" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-yellow-600", children: "These codes can be used to access your account if you lose your authenticator. Each code can only be used once. Store them safely." })] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-neutral-800 rounded p-4 font-mono text-sm space-y-1", children: backupCodes.map((code, i) => ((0, jsx_runtime_1.jsx)("div", { className: "text-cyan-400", children: code }, i))) }), (0, jsx_runtime_1.jsx)("button", { onClick: copyBackupCodes, className: "w-full px-3 py-2 border border-neutral-700 text-neutral-300 hover:bg-neutral-800 rounded text-xs", children: "Copy codes" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
67
+ setStep('idle');
68
+ setBackupCodes(null);
69
+ }, className: "w-full px-3 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-xs", children: "Done" })] })), step === 'disable' && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Enter your password to disable two-factor authentication." }), (0, jsx_runtime_1.jsx)("input", { type: "password", value: disablePassword, onChange: (e) => setDisablePassword(e.target.value), placeholder: "Current password", className: "w-full bg-neutral-800 border border-neutral-700 text-white p-2 rounded text-sm outline-none focus:border-cyan-500" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => {
70
+ setStep('idle');
71
+ setDisablePassword('');
72
+ }, className: "flex-1 px-3 py-1.5 text-neutral-500 hover:text-neutral-300 text-xs", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleDisable, disabled: loading || !disablePassword, className: "flex-1 px-3 py-1.5 bg-red-900 hover:bg-red-800 text-white rounded text-xs disabled:opacity-50", children: loading ? 'Disabling...' : 'Disable 2FA' })] })] }))] }));
73
+ }
@@ -1,5 +1,6 @@
1
1
  export interface MicrocosmQueueStatusPageProps {
2
2
  basePath?: string;
3
3
  onNavigate?: (path: string) => void;
4
+ isAdmin?: boolean;
4
5
  }
5
- export declare function MicrocosmQueueStatusPage({}?: MicrocosmQueueStatusPageProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function MicrocosmQueueStatusPage({ isAdmin }?: MicrocosmQueueStatusPageProps): import("react/jsx-runtime").JSX.Element;
@@ -13,9 +13,11 @@ const LEVEL_LABELS = {
13
13
  warden: 'Warden',
14
14
  admiral: 'Admiral',
15
15
  };
16
- function MicrocosmQueueStatusPage({} = {}) {
16
+ function MicrocosmQueueStatusPage({ isAdmin = false } = {}) {
17
17
  const api = (0, auth_react_1.useMicrocosmApi)();
18
18
  const [userQueue, setUserQueue] = (0, react_1.useState)(null);
19
+ const [adminQueue, setAdminQueue] = (0, react_1.useState)(null);
20
+ const [expansion, setExpansion] = (0, react_1.useState)(null);
19
21
  const [loading, setLoading] = (0, react_1.useState)(true);
20
22
  const [submitting, setSubmitting] = (0, react_1.useState)(false);
21
23
  const [showJoinConfirm, setShowJoinConfirm] = (0, react_1.useState)(false);
@@ -34,7 +36,45 @@ function MicrocosmQueueStatusPage({} = {}) {
34
36
  finally {
35
37
  setLoading(false);
36
38
  }
37
- }, [api]);
39
+ if (isAdmin) {
40
+ try {
41
+ const adminRes = await api.get('/territories/queue/admin');
42
+ setAdminQueue(adminRes?.data ?? adminRes);
43
+ }
44
+ catch { }
45
+ try {
46
+ const expRes = await api.get('/territories/expansion/check');
47
+ setExpansion(expRes?.data ?? expRes);
48
+ }
49
+ catch { }
50
+ }
51
+ }, [api, isAdmin]);
52
+ const handleProcessQueue = async () => {
53
+ setSubmitting(true);
54
+ try {
55
+ await api.post('/territories/queue/process', { batch_size: 50 });
56
+ await loadStatus();
57
+ }
58
+ catch (e) {
59
+ setError(e instanceof Error ? e.message : 'Failed to process queue');
60
+ }
61
+ finally {
62
+ setSubmitting(false);
63
+ }
64
+ };
65
+ const handleTriggerExpansion = async () => {
66
+ setSubmitting(true);
67
+ try {
68
+ await api.post('/territories/expansion/trigger', {});
69
+ await loadStatus();
70
+ }
71
+ catch (e) {
72
+ setError(e instanceof Error ? e.message : 'Failed to trigger expansion');
73
+ }
74
+ finally {
75
+ setSubmitting(false);
76
+ }
77
+ };
38
78
  (0, react_1.useEffect)(() => { loadStatus(); }, [loadStatus]);
39
79
  const handleJoin = async () => {
40
80
  setSubmitting(true);
@@ -69,5 +109,5 @@ function MicrocosmQueueStatusPage({} = {}) {
69
109
  }
70
110
  const level = userQueue?.user_type?.toLowerCase() || 'miner';
71
111
  const levelLabel = LEVEL_LABELS[level] || 'Miner';
72
- return ((0, jsx_runtime_1.jsxs)("div", { className: "max-w-7xl mx-auto px-3 py-4 space-y-3 xs:px-4 xs:space-y-4 sm:px-6 sm:py-6 sm:space-y-6 font-mono", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-lg sm:text-2xl font-bold text-white tracking-wider", children: "Station Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs sm:text-sm text-neutral-400", children: "Your onboarding status" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: loadStatus, className: "px-3 py-1.5 text-xs border border-neutral-700 text-neutral-400 hover:bg-neutral-800 hover:text-white rounded transition-colors", children: "Refresh" })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-3 bg-red-900/20 border border-red-800 rounded text-sm text-red-300", children: error })), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "p-4 bg-neutral-800 rounded mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider", children: "CURRENT LEVEL" }), (0, jsx_runtime_1.jsx)("div", { className: "text-lg font-bold text-cyan-300", children: levelLabel })] }), (0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1 bg-cyan-900/30 text-cyan-300 rounded border border-cyan-700 text-xs", children: ["Lv.", Object.keys(LEVEL_LABELS).indexOf(level) + 3] })] }) }), userQueue?.is_onboarded ? ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-700", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "Onboarded" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-neutral-400", children: ["Assigned to ", userQueue.station_name || userQueue.territory_id || ''] })] }), (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-1 bg-white/20 text-white rounded text-xs", children: "Active" })] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 gap-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 rounded-lg p-4 text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xl font-bold text-white", children: userQueue.territory_id }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Territory ID" })] }) })] })) : userQueue?.in_queue ? ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "In Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Waiting for station assignment" })] }), userQueue.status && ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-1 bg-cyan-900/30 text-cyan-300 rounded text-xs", children: userQueue.status }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: userQueue.position || '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Position" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: userQueue.estimated_wait_minutes || '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Est. wait (min)" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-white truncate", children: userQueue.preferred_territory_id || 'Auto' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Preferred" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-white", children: userQueue.joined_at ? new Date(userQueue.joined_at).toLocaleString() : '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Joined at" })] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCancelConfirm(true), disabled: submitting, className: "w-full py-2 bg-neutral-800 hover:bg-neutral-700 text-white rounded transition-colors disabled:opacity-50", children: "Cancel Queue" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "Not Onboarded" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Join the queue to be assigned a station" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowJoinConfirm(true), disabled: submitting, className: "w-full py-2 bg-white/20 hover:bg-neutral-800 text-white border border-neutral-700 rounded transition-colors disabled:opacity-50", children: "Join Queue" })] }))] }), showJoinConfirm && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setShowJoinConfirm(false), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-md p-6 space-y-4", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-white font-medium", children: "Confirm Join Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "You will be added to the station queue. Auto-assignment happens when a slot opens." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setShowJoinConfirm(false), className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleJoin, disabled: submitting, className: "flex-1 px-3 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Joining...' : 'Confirm' })] })] }) })), showCancelConfirm && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setShowCancelConfirm(false), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-md p-6 space-y-4", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-white font-medium", children: "Cancel Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Are you sure? You will lose your current queue position." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCancelConfirm(false), className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Keep Waiting" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleCancel, disabled: submitting, className: "flex-1 px-3 py-2 bg-red-700 hover:bg-red-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Cancelling...' : 'Confirm Cancel' })] })] }) }))] }));
112
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "max-w-7xl mx-auto px-3 py-4 space-y-3 xs:px-4 xs:space-y-4 sm:px-6 sm:py-6 sm:space-y-6 font-mono", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-lg sm:text-2xl font-bold text-white tracking-wider", children: "Station Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs sm:text-sm text-neutral-400", children: "Your onboarding status" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: loadStatus, className: "px-3 py-1.5 text-xs border border-neutral-700 text-neutral-400 hover:bg-neutral-800 hover:text-white rounded transition-colors", children: "Refresh" })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-3 bg-red-900/20 border border-red-800 rounded text-sm text-red-300", children: error })), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "p-4 bg-neutral-800 rounded mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider", children: "CURRENT LEVEL" }), (0, jsx_runtime_1.jsx)("div", { className: "text-lg font-bold text-cyan-300", children: levelLabel })] }), (0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1 bg-cyan-900/30 text-cyan-300 rounded border border-cyan-700 text-xs", children: ["Lv.", Object.keys(LEVEL_LABELS).indexOf(level) + 3] })] }) }), userQueue?.is_onboarded ? ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-700", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "Onboarded" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-neutral-400", children: ["Assigned to ", userQueue.station_name || userQueue.territory_id || ''] })] }), (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-1 bg-white/20 text-white rounded text-xs", children: "Active" })] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 gap-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 rounded-lg p-4 text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xl font-bold text-white", children: userQueue.territory_id }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Territory ID" })] }) })] })) : userQueue?.in_queue ? ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "In Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Waiting for station assignment" })] }), userQueue.status && ((0, jsx_runtime_1.jsx)("span", { className: "px-2 py-1 bg-cyan-900/30 text-cyan-300 rounded text-xs", children: userQueue.status }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: userQueue.position || '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Position" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: userQueue.estimated_wait_minutes || '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Est. wait (min)" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-white truncate", children: userQueue.preferred_territory_id || 'Auto' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Preferred" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-white", children: userQueue.joined_at ? new Date(userQueue.joined_at).toLocaleString() : '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400", children: "Joined at" })] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCancelConfirm(true), disabled: submitting, className: "w-full py-2 bg-neutral-800 hover:bg-neutral-700 text-white rounded transition-colors disabled:opacity-50", children: "Cancel Queue" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded-lg p-6 border border-neutral-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-white", children: "Not Onboarded" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Join the queue to be assigned a station" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowJoinConfirm(true), disabled: submitting, className: "w-full py-2 bg-white/20 hover:bg-neutral-800 text-white border border-neutral-700 rounded transition-colors disabled:opacity-50", children: "Join Queue" })] }))] }), isAdmin && adminQueue && ((0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { title: "Queue Management (Admin)", children: [(0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mb-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded p-4 text-center border border-neutral-800", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: adminQueue.pending_count ?? 0 }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400 mt-1", children: "Pending" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded p-4 text-center border border-neutral-800", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: adminQueue.processing_count ?? 0 }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400 mt-1", children: "Processing" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded p-4 text-center border border-neutral-800", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-3xl font-bold text-white", children: adminQueue.total_in_queue ?? 0 }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400 mt-1", children: "Total in queue" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-950 rounded p-4 text-center border border-neutral-800", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm text-white", children: adminQueue.oldest_pending ? new Date(adminQueue.oldest_pending).toLocaleString() : '-' }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-400 mt-1", children: "Oldest pending" })] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: handleProcessQueue, disabled: submitting, className: "w-full px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Processing...' : 'Process Queue (batch 50)' })] })), isAdmin && expansion && ((0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { title: "Station Expansion (Admin)", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-neutral-300", children: ["Needs expansion: ", expansion.needs_expansion ? 'YES' : 'NO'] }), expansion.reason && ((0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-neutral-500 mt-1", children: ["Reason: ", expansion.reason] }))] }), (0, jsx_runtime_1.jsx)("button", { onClick: handleTriggerExpansion, disabled: submitting || !expansion.needs_expansion, className: "px-4 py-2 bg-red-900/30 text-red-300 border border-red-800 hover:bg-red-900/50 rounded text-sm disabled:opacity-50", children: submitting ? 'Expanding...' : 'Trigger Expansion' })] }) })), showJoinConfirm && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setShowJoinConfirm(false), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-md p-6 space-y-4", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-white font-medium", children: "Confirm Join Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "You will be added to the station queue. Auto-assignment happens when a slot opens." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setShowJoinConfirm(false), className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleJoin, disabled: submitting, className: "flex-1 px-3 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Joining...' : 'Confirm' })] })] }) })), showCancelConfirm && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setShowCancelConfirm(false), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-md p-6 space-y-4", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-white font-medium", children: "Cancel Queue" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-neutral-400", children: "Are you sure? You will lose your current queue position." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCancelConfirm(false), className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Keep Waiting" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleCancel, disabled: submitting, className: "flex-1 px-3 py-2 bg-red-700 hover:bg-red-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Cancelling...' : 'Confirm Cancel' })] })] }) }))] }));
73
113
  }
@@ -2,5 +2,6 @@ export interface MicrocosmStationListPageProps {
2
2
  basePath?: string;
3
3
  onNavigate?: (path: string) => void;
4
4
  currentUid?: string;
5
+ isAdmin?: boolean;
5
6
  }
6
- export declare function MicrocosmStationListPage({ currentUid }?: MicrocosmStationListPageProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function MicrocosmStationListPage({ currentUid, isAdmin }?: MicrocosmStationListPageProps): import("react/jsx-runtime").JSX.Element;
@@ -6,10 +6,40 @@ const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("react");
7
7
  const auth_react_1 = require("@microcosmmoney/auth-react");
8
8
  const terminal_1 = require("../terminal");
9
+ const API_BASE = 'https://api.microcosm.money/v1';
10
+ const cropToSquare = (file, size) => {
11
+ return new Promise((resolve, reject) => {
12
+ const img = new Image();
13
+ img.onload = () => {
14
+ const canvas = document.createElement('canvas');
15
+ canvas.width = size;
16
+ canvas.height = size;
17
+ const ctx = canvas.getContext('2d');
18
+ if (!ctx) {
19
+ reject(new Error('Canvas not supported'));
20
+ return;
21
+ }
22
+ const side = Math.min(img.width, img.height);
23
+ const sx = (img.width - side) / 2;
24
+ const sy = (img.height - side) / 2;
25
+ ctx.drawImage(img, sx, sy, side, side, 0, 0, size, size);
26
+ canvas.toBlob((blob) => {
27
+ if (!blob) {
28
+ reject(new Error('Blob creation failed'));
29
+ return;
30
+ }
31
+ resolve(new File([blob], file.name, { type: 'image/jpeg' }));
32
+ }, 'image/jpeg', 0.85);
33
+ };
34
+ img.onerror = () => reject(new Error('Image load failed'));
35
+ img.src = URL.createObjectURL(file);
36
+ });
37
+ };
9
38
  const UNIT_LABELS = { station: 'Station', matrix: 'Matrix', sector: 'Sector', system: 'System' };
10
39
  const MAGISTRATE_TITLES = { station: 'Commander', matrix: 'Pioneer', sector: 'Warden', system: 'Admiral' };
11
- function MicrocosmStationListPage({ currentUid } = {}) {
40
+ function MicrocosmStationListPage({ currentUid, isAdmin = false } = {}) {
12
41
  const api = (0, auth_react_1.useMicrocosmApi)();
42
+ const { getAccessToken } = (0, auth_react_1.useMicrocosmContext)();
13
43
  const [units, setUnits] = (0, react_1.useState)([]);
14
44
  const [summary, setSummary] = (0, react_1.useState)(null);
15
45
  const [loading, setLoading] = (0, react_1.useState)(true);
@@ -20,6 +50,10 @@ function MicrocosmStationListPage({ currentUid } = {}) {
20
50
  const [editFormData, setEditFormData] = (0, react_1.useState)({ unit_name: '', description: '' });
21
51
  const [submitting, setSubmitting] = (0, react_1.useState)(false);
22
52
  const [error, setError] = (0, react_1.useState)(null);
53
+ const [imageFile, setImageFile] = (0, react_1.useState)(null);
54
+ const [imagePreview, setImagePreview] = (0, react_1.useState)(null);
55
+ const [uploadingImage, setUploadingImage] = (0, react_1.useState)(false);
56
+ const fileInputRef = (0, react_1.useRef)(null);
23
57
  const loadSummary = (0, react_1.useCallback)(async () => {
24
58
  try {
25
59
  const res = await api.get('/territories/summary');
@@ -75,17 +109,91 @@ function MicrocosmStationListPage({ currentUid } = {}) {
75
109
  const openEditDialog = (unit) => {
76
110
  setEditingUnit(unit);
77
111
  setEditFormData({ unit_name: unit.unit_name, description: unit.description || '' });
112
+ setImageFile(null);
113
+ setImagePreview(null);
114
+ };
115
+ const handleFileSelect = (e) => {
116
+ const file = e.target.files?.[0];
117
+ if (!file)
118
+ return;
119
+ if (!file.type.startsWith('image/')) {
120
+ setError('Please select an image file');
121
+ return;
122
+ }
123
+ if (file.size > 2 * 1024 * 1024) {
124
+ setError('Image must be under 2MB');
125
+ return;
126
+ }
127
+ cropToSquare(file, 512)
128
+ .then(cropped => {
129
+ setImageFile(cropped);
130
+ const reader = new FileReader();
131
+ reader.onload = (ev) => setImagePreview(ev.target?.result);
132
+ reader.readAsDataURL(cropped);
133
+ })
134
+ .catch(() => setError('Image processing failed'));
135
+ };
136
+ const uploadImage = async (unitId) => {
137
+ if (!imageFile)
138
+ return true;
139
+ setUploadingImage(true);
140
+ try {
141
+ const token = await getAccessToken();
142
+ if (!token)
143
+ throw new Error('Not authenticated');
144
+ const formData = new FormData();
145
+ formData.append('image', imageFile, imageFile.name);
146
+ const response = await fetch(`${API_BASE}/territories/${unitId}/image`, {
147
+ method: 'POST',
148
+ headers: { Authorization: `Bearer ${token}` },
149
+ body: formData,
150
+ });
151
+ if (!response.ok) {
152
+ const err = await response.json().catch(() => ({}));
153
+ throw new Error(err.detail || err.error || 'Image upload failed');
154
+ }
155
+ return true;
156
+ }
157
+ catch (e) {
158
+ setError(e instanceof Error ? e.message : 'Image upload failed');
159
+ return false;
160
+ }
161
+ finally {
162
+ setUploadingImage(false);
163
+ }
164
+ };
165
+ const handleImageReview = async (unitId, status) => {
166
+ try {
167
+ await api.put(`/territories/${unitId}/image/review`, { status });
168
+ await loadUnits();
169
+ if (editingUnit && editingUnit.unit_id === unitId) {
170
+ setEditingUnit({ ...editingUnit, image_status: status });
171
+ }
172
+ }
173
+ catch (e) {
174
+ setError(e instanceof Error ? e.message : 'Review failed');
175
+ }
78
176
  };
79
177
  const handleEdit = async () => {
80
178
  if (!editingUnit || !editFormData.unit_name.trim())
81
179
  return;
82
180
  setSubmitting(true);
181
+ setError(null);
83
182
  try {
183
+ if (imageFile) {
184
+ const ok = await uploadImage(editingUnit.unit_id);
185
+ if (!ok) {
186
+ setSubmitting(false);
187
+ return;
188
+ }
189
+ }
84
190
  await api.put(`/territories/${editingUnit.unit_id}`, {
85
191
  unit_name: editFormData.unit_name,
86
192
  description: editFormData.description,
87
193
  });
88
194
  setEditingUnit(null);
195
+ setImageFile(null);
196
+ setImagePreview(null);
89
197
  await loadUnits();
90
198
  }
91
199
  catch (e) {
@@ -98,5 +206,13 @@ function MicrocosmStationListPage({ currentUid } = {}) {
98
206
  return ((0, jsx_runtime_1.jsxs)("div", { className: "max-w-7xl mx-auto px-3 py-4 space-y-3 xs:px-4 xs:space-y-4 sm:px-6 sm:py-6 sm:space-y-6 font-mono", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-lg sm:text-2xl font-bold text-white tracking-wider", children: "Station List" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs sm:text-sm text-neutral-400", children: "All territories (Station / Matrix / Sector / System)" })] }), error && ((0, jsx_runtime_1.jsx)("div", { className: "p-3 bg-red-900/20 border border-red-800 rounded text-sm text-red-300", children: error })), summary && ((0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [(0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "TOTAL STATIONS" }), (0, jsx_runtime_1.jsx)("div", { className: "text-2xl font-bold text-white", children: summary.total_stations ?? 0 })] }), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "TOTAL MEMBERS" }), (0, jsx_runtime_1.jsx)("div", { className: "text-2xl font-bold text-cyan-400", children: summary.total_members ?? 0 })] }), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "VAULT MCD" }), (0, jsx_runtime_1.jsx)("div", { className: "text-2xl font-bold text-cyan-400", children: (summary.total_vault_mcd ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 }) })] }), (0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 tracking-wider mb-1", children: "AVG KPI" }), (0, jsx_runtime_1.jsx)("div", { className: "text-2xl font-bold text-white", children: (summary.avg_kpi_score ?? 0).toFixed(1) })] })] })), (0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { children: (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col md:flex-row gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex gap-2 flex-wrap", children: ['all', 'station', 'matrix', 'sector', 'system'].map(f => ((0, jsx_runtime_1.jsx)("button", { onClick: () => setFilter(f), className: `px-3 py-1.5 text-sm rounded transition-colors ${filter === f ? 'bg-cyan-700 text-white' : 'bg-neutral-800 text-neutral-400 hover:bg-neutral-700 hover:text-white'}`, children: f === 'all' ? 'All' : UNIT_LABELS[f] }, f))) }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: "Search by name / location / short_id...", className: "flex-1 bg-neutral-800 border border-neutral-600 text-white rounded px-3 py-1.5 text-sm focus:outline-none focus:border-cyan-400" })] }) }), loading ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center py-20", children: (0, jsx_runtime_1.jsx)("span", { className: "text-neutral-400", children: "Loading units..." }) })) : filteredUnits.length === 0 ? ((0, jsx_runtime_1.jsx)(terminal_1.TerminalCard, { children: (0, jsx_runtime_1.jsx)("div", { className: "text-center py-8 text-neutral-500", children: "No units match your filter" }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: filteredUnits.map(unit => {
99
207
  const metrics = unitDataCache[unit.unit_id];
100
208
  return ((0, jsx_runtime_1.jsxs)(terminal_1.TerminalCard, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-white font-semibold", children: unit.unit_name }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-neutral-500", children: [UNIT_LABELS[unit.unit_type], " \u00B7 ", MAGISTRATE_TITLES[unit.unit_type]] }), unit.short_id && ((0, jsx_runtime_1.jsx)("div", { className: "text-xs font-mono text-cyan-400 mt-1", children: unit.short_id }))] }), canEditUnit(unit) && ((0, jsx_runtime_1.jsx)("button", { onClick: () => openEditDialog(unit), className: "text-xs px-2 py-1 border border-neutral-700 text-neutral-400 hover:text-white hover:bg-neutral-800 rounded", children: "Edit" }))] }), unit.description && ((0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-400 mb-3 line-clamp-2", children: unit.description })), metrics && ((0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 gap-2 pt-3 border-t border-neutral-700", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500", children: "Members" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-white", children: [metrics.member_count, "/", metrics.max_capacity] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500", children: "Occupancy" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-cyan-400", children: [(metrics.occupancy_rate * 100).toFixed(0), "%"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-span-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500", children: "Vault MCD" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-white", children: (metrics.vault_mcd ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 }) })] })] })), unit.image_status === 'pending' && ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 text-xs text-yellow-400", children: "Image pending review" }))] }, unit.unit_id));
101
- }) })), editingUnit && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setEditingUnit(null), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-lg p-6 space-y-4", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("h3", { className: "text-white font-medium", children: ["Edit ", editingUnit.unit_name] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-neutral-400 tracking-wider block mb-1", children: "Name" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: editFormData.unit_name, onChange: (e) => setEditFormData({ ...editFormData, unit_name: e.target.value }), className: "w-full bg-neutral-800 border border-neutral-600 text-white rounded px-3 py-2 text-sm" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-neutral-400 tracking-wider block mb-1", children: "Description" }), (0, jsx_runtime_1.jsx)("textarea", { value: editFormData.description, onChange: (e) => setEditFormData({ ...editFormData, description: e.target.value }), rows: 4, className: "w-full bg-neutral-800 border border-neutral-600 text-white rounded px-3 py-2 text-sm" })] }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-neutral-500", children: "Note: image upload available on main portal only" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setEditingUnit(null), className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleEdit, disabled: submitting || !editFormData.unit_name.trim(), className: "flex-1 px-3 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: submitting ? 'Saving...' : 'Save' })] })] }) }))] }));
209
+ }) })), editingUnit && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4", onClick: () => setEditingUnit(null), children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-neutral-900 border border-neutral-700 rounded-lg w-full max-w-lg p-6 space-y-4 max-h-[90vh] overflow-y-auto", onClick: e => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("h3", { className: "text-white font-medium", children: ["Edit ", editingUnit.unit_name] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-neutral-400 tracking-wider block mb-1", children: "Name" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: editFormData.unit_name, onChange: (e) => setEditFormData({ ...editFormData, unit_name: e.target.value }), className: "w-full bg-neutral-800 border border-neutral-600 text-white rounded px-3 py-2 text-sm" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-neutral-400 tracking-wider block mb-1", children: "Description" }), (0, jsx_runtime_1.jsx)("textarea", { value: editFormData.description, onChange: (e) => setEditFormData({ ...editFormData, description: e.target.value }), rows: 4, className: "w-full bg-neutral-800 border border-neutral-600 text-white rounded px-3 py-2 text-sm" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-neutral-400 tracking-wider block mb-1", children: "Image" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [(imagePreview || editingUnit.image_url) ? ((0, jsx_runtime_1.jsx)("img", { src: imagePreview || editingUnit.image_url, alt: "", className: "w-24 h-24 rounded object-cover border border-neutral-700" })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-24 h-24 rounded bg-neutral-800 border border-neutral-700 flex items-center justify-center text-xs text-neutral-500", children: "No image" })), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 space-y-2", children: [(0, jsx_runtime_1.jsx)("input", { ref: fileInputRef, type: "file", accept: "image/jpeg,image/png,image/webp", onChange: handleFileSelect, className: "hidden" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => fileInputRef.current?.click(), disabled: uploadingImage, className: "w-full px-3 py-1.5 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 hover:text-white rounded text-sm", children: imageFile ? 'Change' : 'Select image' }), imageFile && ((0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-neutral-500", children: [imageFile.name, " (", (imageFile.size / 1024).toFixed(1), "KB)"] })), editingUnit.image_status && ((0, jsx_runtime_1.jsx)("div", { className: `text-xs px-2 py-0.5 rounded inline-block ${editingUnit.image_status === 'approved'
210
+ ? 'bg-green-900/30 text-green-400'
211
+ : editingUnit.image_status === 'rejected'
212
+ ? 'bg-red-900/30 text-red-400'
213
+ : 'bg-yellow-900/30 text-yellow-400'}`, children: editingUnit.image_status }))] })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-neutral-500 mt-2", children: "Max 2MB. Auto-cropped to square (512\u00D7512). JPG/PNG/WebP." })] }), isAdmin && editingUnit.image_url && editingUnit.image_status === 'pending' && ((0, jsx_runtime_1.jsxs)("div", { className: "pt-3 border-t border-neutral-700 space-y-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-xs text-cyan-400 tracking-wider", children: "ADMIN REVIEW" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => handleImageReview(editingUnit.unit_id, 'approved'), className: "flex-1 px-3 py-1.5 bg-green-900/30 text-green-400 hover:bg-green-900/50 border border-green-800 rounded text-sm", children: "Approve" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => handleImageReview(editingUnit.unit_id, 'rejected'), className: "flex-1 px-3 py-1.5 bg-red-900/30 text-red-400 hover:bg-red-900/50 border border-red-800 rounded text-sm", children: "Reject" })] })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2 pt-2", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => {
214
+ setEditingUnit(null);
215
+ setImageFile(null);
216
+ setImagePreview(null);
217
+ }, className: "flex-1 px-3 py-2 border border-neutral-700 text-neutral-400 hover:bg-neutral-800 rounded text-sm", children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleEdit, disabled: submitting || uploadingImage || !editFormData.unit_name.trim(), className: "flex-1 px-3 py-2 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-sm disabled:opacity-50", children: uploadingImage ? 'Uploading...' : submitting ? 'Saving...' : 'Save' })] })] }) }))] }));
102
218
  }
package/dist/index.d.ts CHANGED
@@ -79,3 +79,7 @@ export { MicrocosmRewardsPage } from './components/rewards/rewards-page';
79
79
  export type { MicrocosmRewardsPageProps } from './components/rewards/rewards-page';
80
80
  export { MicrocosmProfilePage } from './components/profile/profile-page';
81
81
  export type { MicrocosmProfilePageProps } from './components/profile/profile-page';
82
+ export { MicrocosmEmailChangeCard } from './components/profile/email-change-card';
83
+ export type { MicrocosmEmailChangeCardProps } from './components/profile/email-change-card';
84
+ export { MicrocosmTwoFactorSettings } from './components/profile/two-factor-settings';
85
+ export type { MicrocosmTwoFactorSettingsProps } from './components/profile/two-factor-settings';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MicrocosmTerritoryPage = exports.MicrocosmAuctionPage = exports.MicrocosmMCDPage = exports.MicrocosmMiningPage = exports.MicrocosmWalletPage = exports.MicrocosmLendingPage = exports.MicrocosmFragmentPage = exports.MicrocosmLockPeriods = exports.MicrocosmMCDStats = exports.MicrocosmMCCTokenStats = exports.MicrocosmEcosystemStats = exports.MicrocosmMyMining = exports.MicrocosmMiningWeight = exports.MicrocosmMintingStats = exports.MicrocosmPriceChart = exports.MicrocosmAssetsSummary = exports.MicrocosmQuickActions = exports.MicrocosmMarketBar = exports.MicrocosmDashboardOverview = exports.KPIRadialChart = exports.VoteResultBar = exports.MiningProgressBar = exports.TerritoryCard = exports.TerminalTooltip = exports.TerminalInput = exports.TerminalCountdown = exports.TerminalDialog = exports.TerminalTabs = exports.TerminalTable = exports.TerminalProgress = exports.TerminalDataRow = exports.TerminalBadge = exports.TerminalEmpty = exports.TerminalError = exports.TerminalLoading = exports.TerminalPageHeader = exports.TerminalCommand = exports.StatBox = exports.TerminalCard = exports.getMenuDescription = exports.getMenuTitle = exports.resolveMenuPath = exports.getAllMenuItems = exports.microcosmMenuGroups = exports.dashboardMenu = exports.web3OsMenu = exports.blockchainMenu = exports.useLinkComponent = exports.LinkProvider = exports.MicrocosmMenuSection = void 0;
4
- exports.MicrocosmProfilePage = exports.MicrocosmRewardsPage = exports.MicrocosmStationListPage = exports.MicrocosmQueueStatusPage = exports.MicrocosmManagerIncomePage = exports.MicrocosmReincarnationPage = exports.MicrocosmVotingPage = exports.MicrocosmOrganizationPage = void 0;
4
+ exports.MicrocosmTwoFactorSettings = exports.MicrocosmEmailChangeCard = exports.MicrocosmProfilePage = exports.MicrocosmRewardsPage = exports.MicrocosmStationListPage = exports.MicrocosmQueueStatusPage = exports.MicrocosmManagerIncomePage = exports.MicrocosmReincarnationPage = exports.MicrocosmVotingPage = exports.MicrocosmOrganizationPage = void 0;
5
5
  var menu_section_1 = require("./components/menu-section");
6
6
  Object.defineProperty(exports, "MicrocosmMenuSection", { enumerable: true, get: function () { return menu_section_1.MicrocosmMenuSection; } });
7
7
  var link_context_1 = require("./link-context");
@@ -101,3 +101,7 @@ var rewards_page_1 = require("./components/rewards/rewards-page");
101
101
  Object.defineProperty(exports, "MicrocosmRewardsPage", { enumerable: true, get: function () { return rewards_page_1.MicrocosmRewardsPage; } });
102
102
  var profile_page_1 = require("./components/profile/profile-page");
103
103
  Object.defineProperty(exports, "MicrocosmProfilePage", { enumerable: true, get: function () { return profile_page_1.MicrocosmProfilePage; } });
104
+ var email_change_card_1 = require("./components/profile/email-change-card");
105
+ Object.defineProperty(exports, "MicrocosmEmailChangeCard", { enumerable: true, get: function () { return email_change_card_1.MicrocosmEmailChangeCard; } });
106
+ var two_factor_settings_1 = require("./components/profile/two-factor-settings");
107
+ Object.defineProperty(exports, "MicrocosmTwoFactorSettings", { enumerable: true, get: function () { return two_factor_settings_1.MicrocosmTwoFactorSettings; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microcosmmoney/portal-react",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "description": "Microcosm Portal UI components for React/Next.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@microcosmmoney/auth-core": "^2.3.2",
22
- "@microcosmmoney/auth-react": "^2.4.0"
22
+ "@microcosmmoney/auth-react": "^2.5.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/react": "^18.0.0",