@microcosmmoney/portal-react 3.7.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
+ }
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.7.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",