smaller fixes
This commit is contained in:
parent
8e395aa8bb
commit
db46670a99
@ -7,7 +7,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
padding: 1.25rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dev-onboarding-embedded {
|
||||
@ -22,10 +23,27 @@
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 580px;
|
||||
padding: 2.25rem;
|
||||
max-height: calc(100vh - 2.5rem);
|
||||
overflow-y: auto;
|
||||
padding: 1.75rem;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
color: var(--text-color, #e2e8f0);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
||||
}
|
||||
|
||||
.dev-modal-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.dev-modal-container::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dev-modal-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dev-modal-close {
|
||||
@ -372,46 +390,47 @@
|
||||
.dev-monetization-box {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
border-radius: 10px;
|
||||
padding: 0.85rem 1rem;
|
||||
}
|
||||
|
||||
.dev-monetization-header {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
gap: 0.65rem;
|
||||
align-items: center;
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.dev-monetization-header h4 {
|
||||
margin: 0 0 0.2rem 0;
|
||||
margin: 0 0 0.15rem 0;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.dev-monetization-header p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.dev-check-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 1rem 0 0 0;
|
||||
margin: 0.5rem 0 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.dev-check-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.8rem;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.dev-check-list svg {
|
||||
color: #22c55e;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export const DeveloperOnboardingModal: React.FC<DeveloperOnboardingModalProps> =
|
||||
embedded = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { user, login } = useAuth();
|
||||
const { user, login, refreshUser } = useAuth();
|
||||
const [step, setStep] = useState<number>(1);
|
||||
const [entityType, setEntityType] = useState<'individual' | 'organization'>('individual');
|
||||
const [orgType, setOrgType] = useState<string>('');
|
||||
@ -582,117 +582,93 @@ export const DeveloperOnboardingModal: React.FC<DeveloperOnboardingModalProps> =
|
||||
{/* ── STEP 5: Finish & Confirmation ── */}
|
||||
{step === 5 && (
|
||||
<div className="dev-step-content">
|
||||
{!isSellingPaid ? (
|
||||
<>
|
||||
<div className="dev-monetization-box">
|
||||
<div className="dev-monetization-header">
|
||||
<Gift size={22} color="#10b981" />
|
||||
<div>
|
||||
<h4>Ready to Publish Free Plugins!</h4>
|
||||
<p>Your developer profile is configured for free & open-source uploads.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="dev-check-list">
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Publish unlimited free plugins ($0 fee)</li>
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Full analytics and review dashboard</li>
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Option to add address & sell paid plugins anytime later</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* ── Security Requirement for Developers ── */}
|
||||
{!hasSecureAuth && (
|
||||
<div style={{ marginTop: '1.25rem' }}>
|
||||
<SecuritySetupCards
|
||||
title="Developer Security Requirement"
|
||||
description="To publish plugins and protect developer payouts, you must set up a Passkey or Two-Factor Authentication before completing onboarding."
|
||||
isRequired={true}
|
||||
onComplete={() => {}}
|
||||
/>
|
||||
</div>
|
||||
<div className="dev-monetization-box" style={{ marginBottom: '0.85rem' }}>
|
||||
<div className="dev-monetization-header">
|
||||
{!isSellingPaid ? (
|
||||
<Gift size={20} color="#10b981" />
|
||||
) : (
|
||||
<CreditCard size={20} color="#f97316" />
|
||||
)}
|
||||
|
||||
{/* ── Mandatory Legal Consent ── */}
|
||||
<div style={{
|
||||
marginTop: '1.25rem',
|
||||
padding: '0.85rem 1rem',
|
||||
background: 'rgba(255, 183, 77, 0.06)',
|
||||
border: '1px solid rgba(255, 183, 77, 0.25)',
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.65rem', cursor: 'pointer', fontSize: '0.78rem', color: '#cbd5e1', lineHeight: 1.5 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={acceptedTerms}
|
||||
onChange={e => setAcceptedTerms(e.target.checked)}
|
||||
style={{ marginTop: '0.15rem', accentColor: '#f97316', width: 16, height: 16, flexShrink: 0, cursor: 'pointer' }}
|
||||
/>
|
||||
<span>
|
||||
I agree to the <a href="/developer-terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline', fontWeight: 600 }}>Developer Distribution Agreement</a>, <a href="/terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Terms of Service</a>, and <a href="/guidelines" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Review Guidelines</a>. I grant Pumpkin Marketplace the worldwide license to distribute my plugins, acknowledge platform moderation and takedown authority, and confirm my plugins comply with Mojang's Minecraft EULA.
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '0.4rem' }}>
|
||||
<h4 style={{ margin: 0, fontSize: '0.95rem', fontWeight: 600, color: '#fff' }}>
|
||||
{!isSellingPaid ? 'Free Creator Profile' : 'Commercial Seller Profile'}
|
||||
</h4>
|
||||
<span style={{
|
||||
fontSize: '0.72rem',
|
||||
fontWeight: 600,
|
||||
padding: '0.15rem 0.5rem',
|
||||
borderRadius: 999,
|
||||
background: !isSellingPaid ? 'rgba(16, 185, 129, 0.15)' : 'rgba(249, 115, 22, 0.15)',
|
||||
color: !isSellingPaid ? '#34d399' : '#f97316',
|
||||
border: `1px solid ${!isSellingPaid ? 'rgba(16, 185, 129, 0.3)' : 'rgba(249, 115, 22, 0.3)'}`,
|
||||
}}>
|
||||
{!isSellingPaid ? 'Free Uploads' : 'Paid + Stripe'}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p style={{ margin: '0.2rem 0 0', fontSize: '0.78rem', color: 'var(--text-muted, #94a3b8)' }}>
|
||||
Display: <strong style={{ color: '#fff' }}>{displayName}</strong> • Email: <strong style={{ color: '#fff' }}>{supportEmail}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dev-btn-row-stacked" style={{ marginTop: '1.25rem' }}>
|
||||
<button
|
||||
className="dev-btn dev-btn-primary"
|
||||
disabled={loading || !acceptedTerms || !hasSecureAuth}
|
||||
onClick={() => handleCompleteOnboarding(false)}
|
||||
>
|
||||
{loading ? 'Completing registration...' : t('developer.onboarding.btn_complete')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
{/* ── Security Requirement for Developers ── */}
|
||||
{hasSecureAuth ? (
|
||||
<div style={{
|
||||
padding: '0.65rem 0.85rem',
|
||||
background: 'rgba(34, 197, 94, 0.08)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.25)',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.55rem',
|
||||
color: '#4ade80',
|
||||
fontSize: '0.82rem',
|
||||
marginBottom: '0.85rem',
|
||||
}}>
|
||||
<CheckCircle2 size={16} style={{ flexShrink: 0 }} />
|
||||
<span><strong>Security Verified:</strong> 2FA / Passkey protection is active on your account.</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="dev-monetization-box">
|
||||
<div className="dev-monetization-header">
|
||||
<CreditCard size={22} color="#f97316" />
|
||||
<div>
|
||||
<h4>{t('developer.onboarding.intent_paid')}</h4>
|
||||
<p>{t('developer.onboarding.intent_paid_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="dev-check-list">
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Automated payouts directly to your bank account</li>
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Set custom pricing, discounts, and coupons</li>
|
||||
<li><CheckCircle2 size={16} color="#10b981" /> Automated license key generation for buyers</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style={{ marginBottom: '0.85rem' }}>
|
||||
<SecuritySetupCards
|
||||
compact={true}
|
||||
title="Developer Security Requirement"
|
||||
description="To publish plugins, set up Passkey or 2FA Authenticator before finishing."
|
||||
isRequired={true}
|
||||
onComplete={async () => {
|
||||
if (refreshUser) await refreshUser();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Security Requirement for Developers ── */}
|
||||
{!hasSecureAuth && (
|
||||
<div style={{ marginTop: '1.25rem' }}>
|
||||
<SecuritySetupCards
|
||||
title="Developer Security Requirement"
|
||||
description="To publish plugins and protect developer payouts, you must set up a Passkey or Two-Factor Authentication before completing onboarding."
|
||||
isRequired={true}
|
||||
onComplete={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* ── Mandatory Legal Consent ── */}
|
||||
<div style={{
|
||||
padding: '0.75rem 0.85rem',
|
||||
background: 'rgba(255, 183, 77, 0.05)',
|
||||
border: '1px solid rgba(255, 183, 77, 0.2)',
|
||||
borderRadius: 8,
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.55rem', cursor: 'pointer', fontSize: '0.76rem', color: '#cbd5e1', lineHeight: 1.45 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={acceptedTerms}
|
||||
onChange={e => setAcceptedTerms(e.target.checked)}
|
||||
style={{ marginTop: '0.15rem', accentColor: '#f97316', width: 15, height: 15, flexShrink: 0, cursor: 'pointer' }}
|
||||
/>
|
||||
<span>
|
||||
I agree to the <a href="/developer-terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline', fontWeight: 600 }}>Developer Distribution Agreement</a>, <a href="/terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Terms of Service</a>, and <a href="/guidelines" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Review Guidelines</a>. I grant Pumpkin Market license to distribute my plugins and confirm compliance with Mojang's Minecraft EULA.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* ── Mandatory Legal Consent ── */}
|
||||
<div style={{
|
||||
marginTop: '1.25rem',
|
||||
padding: '0.85rem 1rem',
|
||||
background: 'rgba(255, 183, 77, 0.06)',
|
||||
border: '1px solid rgba(255, 183, 77, 0.25)',
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.65rem', cursor: 'pointer', fontSize: '0.78rem', color: '#cbd5e1', lineHeight: 1.5 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={acceptedTerms}
|
||||
onChange={e => setAcceptedTerms(e.target.checked)}
|
||||
style={{ marginTop: '0.15rem', accentColor: '#f97316', width: 16, height: 16, flexShrink: 0, cursor: 'pointer' }}
|
||||
/>
|
||||
<span>
|
||||
I agree to the <a href="/developer-terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline', fontWeight: 600 }}>Developer Distribution Agreement</a>, <a href="/terms" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Terms of Service</a>, and <a href="/guidelines" target="_blank" rel="noopener noreferrer" style={{ color: '#ffb74d', textDecoration: 'underline' }}>Review Guidelines</a>. I grant Pumpkin Marketplace the worldwide license to distribute my plugins, acknowledge platform moderation and takedown authority, and confirm my plugins comply with Mojang's Minecraft EULA.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="dev-btn-row-stacked" style={{ marginTop: '1.25rem' }}>
|
||||
{/* Actions */}
|
||||
<div className="dev-btn-row-stacked">
|
||||
{isSellingPaid ? (
|
||||
<>
|
||||
<button
|
||||
className="dev-btn dev-btn-stripe"
|
||||
disabled={loading || !acceptedTerms || !hasSecureAuth}
|
||||
@ -707,12 +683,22 @@ export const DeveloperOnboardingModal: React.FC<DeveloperOnboardingModalProps> =
|
||||
>
|
||||
{t('developer.onboarding.btn_complete')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
className="dev-btn dev-btn-primary"
|
||||
disabled={loading || !acceptedTerms || !hasSecureAuth}
|
||||
onClick={() => handleCompleteOnboarding(false)}
|
||||
>
|
||||
{loading ? 'Completing registration...' : t('developer.onboarding.btn_complete')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="dev-btn-row" style={{ marginTop: '1rem' }}>
|
||||
<button className="dev-btn dev-btn-secondary" onClick={prevStep}>{t('developer.onboarding.btn_back')}</button>
|
||||
<div className="dev-btn-row" style={{ marginTop: '0.85rem' }}>
|
||||
<button className="dev-btn dev-btn-secondary" onClick={prevStep}>
|
||||
{t('developer.onboarding.btn_back')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||
import { useAuth } from '../App';
|
||||
import api from '../api';
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { RecoveryCodesModal } from './RecoveryCodesModal';
|
||||
import {
|
||||
Fingerprint,
|
||||
@ -24,6 +25,7 @@ interface SecuritySetupCardsProps {
|
||||
onSkip?: () => void;
|
||||
canSkip?: boolean;
|
||||
isRequired?: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
@ -33,6 +35,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
onSkip,
|
||||
canSkip = false,
|
||||
isRequired = false,
|
||||
compact = false,
|
||||
}) => {
|
||||
const { login, user, refreshUser } = useAuth();
|
||||
|
||||
@ -50,7 +53,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
|
||||
// TOTP State
|
||||
const [totpLoading, setTotpLoading] = useState(false);
|
||||
const [totpQr, setTotpQr] = useState<string | null>(null);
|
||||
const [totpUri, setTotpUri] = useState<string | null>(null);
|
||||
const [totpSecret, setTotpSecret] = useState<string | null>(null);
|
||||
const [totpCode, setTotpCode] = useState('');
|
||||
const [totpVerifying, setTotpVerifying] = useState(false);
|
||||
@ -63,7 +66,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
setExpandedBox(null);
|
||||
} else {
|
||||
setExpandedBox(box);
|
||||
if (box === 'totp' && !totpQr && !totpSuccess) {
|
||||
if (box === 'totp' && !totpUri && !totpSuccess) {
|
||||
loadTotpSetup();
|
||||
}
|
||||
}
|
||||
@ -74,7 +77,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
setTotpLoading(true);
|
||||
setTotpError(null);
|
||||
const res = await api.post('/user/2fa/setup');
|
||||
setTotpQr(res.data.qrCode);
|
||||
setTotpUri(res.data.uri);
|
||||
setTotpSecret(res.data.secret);
|
||||
} catch (err: any) {
|
||||
setTotpError(err.response?.data?.error || 'Failed to initialize 2FA setup.');
|
||||
@ -172,7 +175,14 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
const hasSecureAuth = Boolean(user?.totp_enabled || (user?.passkey_count && user.passkey_count > 0) || user?.has_passkey);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div style={compact ? {
|
||||
width: '100%',
|
||||
padding: '0.85rem',
|
||||
background: 'rgba(255,255,255,0.02)',
|
||||
borderRadius: 12,
|
||||
border: '1px solid var(--mp-border, rgba(255,255,255,0.08))',
|
||||
boxSizing: 'border-box',
|
||||
} : {
|
||||
maxWidth: 680,
|
||||
width: '100%',
|
||||
margin: '0 auto',
|
||||
@ -181,67 +191,112 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
borderRadius: 20,
|
||||
border: '1px solid var(--mp-border, rgba(255,255,255,0.08))',
|
||||
boxShadow: '0 20px 40px -15px rgba(0,0,0,0.5)',
|
||||
boxSizing: 'border-box',
|
||||
}}>
|
||||
{/* Header */}
|
||||
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
|
||||
<div style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(234, 88, 12, 0.05))',
|
||||
border: '1px solid rgba(249, 115, 22, 0.3)',
|
||||
color: 'var(--mp-accent, #f97316)',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
<Shield size={28} />
|
||||
{compact ? (
|
||||
<div style={{ marginBottom: '0.85rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.55rem' }}>
|
||||
<div style={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 6,
|
||||
background: 'rgba(249, 115, 22, 0.15)',
|
||||
color: 'var(--mp-accent, #f97316)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Shield size={16} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 style={{ margin: 0, fontSize: '0.9rem', fontWeight: 700, color: 'var(--mp-text, #f8fafc)' }}>
|
||||
{title}
|
||||
</h4>
|
||||
<p style={{ margin: 0, fontSize: '0.76rem', color: 'var(--mp-muted, #94a3b8)', lineHeight: 1.3 }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isRequired && (
|
||||
<span style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.35rem',
|
||||
padding: '0.15rem 0.5rem',
|
||||
background: 'rgba(239, 68, 68, 0.12)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.25)',
|
||||
borderRadius: 9999,
|
||||
color: '#f87171',
|
||||
fontSize: '0.72rem',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
<AlertCircle size={12} /> Required
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h2 style={{
|
||||
fontSize: '1.6rem',
|
||||
fontWeight: 700,
|
||||
margin: '0 0 0.5rem 0',
|
||||
color: 'var(--mp-text, #f8fafc)',
|
||||
letterSpacing: '-0.02em',
|
||||
}}>
|
||||
{title}
|
||||
</h2>
|
||||
<p style={{
|
||||
color: 'var(--mp-muted, #94a3b8)',
|
||||
fontSize: '0.95rem',
|
||||
lineHeight: 1.5,
|
||||
maxWidth: 540,
|
||||
margin: '0 auto',
|
||||
}}>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
{isRequired && (
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
|
||||
<div style={{
|
||||
marginTop: '1rem',
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(234, 88, 12, 0.05))',
|
||||
border: '1px solid rgba(249, 115, 22, 0.3)',
|
||||
color: 'var(--mp-accent, #f97316)',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.4rem 0.85rem',
|
||||
background: 'rgba(239, 68, 68, 0.12)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.25)',
|
||||
borderRadius: 9999,
|
||||
color: '#f87171',
|
||||
fontSize: '0.82rem',
|
||||
fontWeight: 600,
|
||||
justifyContent: 'center',
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
<AlertCircle size={14} /> Required for Plugin Publishing & Developer Verification
|
||||
<Shield size={28} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h2 style={{
|
||||
fontSize: '1.6rem',
|
||||
fontWeight: 700,
|
||||
margin: '0 0 0.5rem 0',
|
||||
color: 'var(--mp-text, #f8fafc)',
|
||||
letterSpacing: '-0.02em',
|
||||
}}>
|
||||
{title}
|
||||
</h2>
|
||||
<p style={{
|
||||
color: 'var(--mp-muted, #94a3b8)',
|
||||
fontSize: '0.95rem',
|
||||
lineHeight: 1.5,
|
||||
maxWidth: 540,
|
||||
margin: '0 auto',
|
||||
}}>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
{isRequired && (
|
||||
<div style={{
|
||||
marginTop: '1rem',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.4rem 0.85rem',
|
||||
background: 'rgba(239, 68, 68, 0.12)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.25)',
|
||||
borderRadius: 9999,
|
||||
color: '#f87171',
|
||||
fontSize: '0.82rem',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
<AlertCircle size={14} /> Required for Plugin Publishing & Developer Verification
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Boxes Grid */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: compact ? '0.65rem' : '1rem' }}>
|
||||
{/* ── Box 1: Passkey / Biometrics (Recommended) ── */}
|
||||
<div style={{
|
||||
border: expandedBox === 'passkey' ? '1px solid var(--mp-accent, #f97316)' : '1px solid var(--mp-border, rgba(255,255,255,0.08))',
|
||||
borderRadius: 14,
|
||||
borderRadius: compact ? 10 : 14,
|
||||
background: expandedBox === 'passkey' ? 'rgba(249, 115, 22, 0.03)' : 'rgba(255,255,255,0.02)',
|
||||
transition: 'all 0.2s ease',
|
||||
overflow: 'hidden',
|
||||
@ -251,7 +306,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
onClick={() => toggleBox('passkey')}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '1.25rem 1.5rem',
|
||||
padding: compact ? '0.75rem 0.9rem' : '1.25rem 1.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
@ -261,11 +316,11 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: compact ? '0.75rem' : '1rem' }}>
|
||||
<div style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 12,
|
||||
width: compact ? 36 : 44,
|
||||
height: compact ? 36 : 44,
|
||||
borderRadius: compact ? 8 : 12,
|
||||
background: 'linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(34, 197, 94, 0.05))',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
color: '#4ade80',
|
||||
@ -274,55 +329,55 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Fingerprint size={24} />
|
||||
<Fingerprint size={compact ? 20 : 24} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', marginBottom: '0.2rem' }}>
|
||||
<span style={{ fontWeight: 700, fontSize: '1.05rem', color: 'var(--mp-text, #f8fafc)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.15rem' }}>
|
||||
<span style={{ fontWeight: 700, fontSize: compact ? '0.92rem' : '1.05rem', color: 'var(--mp-text, #f8fafc)' }}>
|
||||
Passkey & Biometrics
|
||||
</span>
|
||||
<span style={{
|
||||
fontSize: '0.72rem',
|
||||
fontSize: '0.68rem',
|
||||
fontWeight: 700,
|
||||
padding: '0.15rem 0.55rem',
|
||||
padding: '0.1rem 0.45rem',
|
||||
borderRadius: 999,
|
||||
background: 'rgba(34, 197, 94, 0.15)',
|
||||
color: '#4ade80',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.25rem',
|
||||
gap: '0.2rem',
|
||||
}}>
|
||||
<Sparkles size={11} /> Recommended
|
||||
<Sparkles size={10} /> Recommended
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ margin: 0, fontSize: '0.85rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
<p style={{ margin: 0, fontSize: compact ? '0.75rem' : '0.85rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
Fastest & most secure. Touch ID, Face ID, Windows Hello, or Security Key.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ color: 'var(--mp-muted, #94a3b8)', marginLeft: '1rem' }}>
|
||||
{expandedBox === 'passkey' ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
|
||||
<div style={{ color: 'var(--mp-muted, #94a3b8)', marginLeft: '0.5rem' }}>
|
||||
{expandedBox === 'passkey' ? <ChevronUp size={compact ? 18 : 20} /> : <ChevronDown size={compact ? 18 : 20} />}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Passkey Expanded Content */}
|
||||
{expandedBox === 'passkey' && (
|
||||
<div style={{
|
||||
padding: '0 1.5rem 1.5rem 1.5rem',
|
||||
padding: compact ? '0 0.9rem 0.9rem 0.9rem' : '0 1.5rem 1.5rem 1.5rem',
|
||||
borderTop: '1px solid rgba(255,255,255,0.06)',
|
||||
paddingTop: '1.25rem',
|
||||
paddingTop: compact ? '0.85rem' : '1.25rem',
|
||||
}}>
|
||||
<div style={{
|
||||
padding: '0.9rem 1rem',
|
||||
padding: compact ? '0.65rem 0.8rem' : '0.9rem 1rem',
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
borderRadius: 10,
|
||||
marginBottom: '1.25rem',
|
||||
fontSize: '0.85rem',
|
||||
borderRadius: 8,
|
||||
marginBottom: '1rem',
|
||||
fontSize: compact ? '0.78rem' : '0.85rem',
|
||||
color: 'var(--mp-muted, #94a3b8)',
|
||||
lineHeight: 1.5,
|
||||
lineHeight: 1.45,
|
||||
}}>
|
||||
<strong>How it works:</strong> Passkeys replace passwords and SMS codes with cryptographic keys stored securely in your device’s keychain (Apple Keychain, Google Password Manager, Windows Hello, 1Password, or YubiKey). They are 100% phishing-proof.
|
||||
<strong>How it works:</strong> Passkeys replace passwords and SMS codes with cryptographic keys stored securely in your device's keychain. They are 100% phishing-proof.
|
||||
</div>
|
||||
|
||||
{passkeyError && (
|
||||
@ -330,15 +385,15 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.75rem 1rem',
|
||||
padding: '0.65rem 0.85rem',
|
||||
background: 'rgba(239, 68, 68, 0.1)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.25)',
|
||||
borderRadius: 8,
|
||||
color: '#f87171',
|
||||
fontSize: '0.85rem',
|
||||
marginBottom: '1rem',
|
||||
fontSize: '0.82rem',
|
||||
marginBottom: '0.85rem',
|
||||
}}>
|
||||
<AlertCircle size={16} />
|
||||
<AlertCircle size={15} />
|
||||
<span>{passkeyError}</span>
|
||||
</div>
|
||||
)}
|
||||
@ -348,21 +403,21 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.85rem 1rem',
|
||||
padding: '0.75rem 1rem',
|
||||
background: 'rgba(34, 197, 94, 0.12)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
borderRadius: 8,
|
||||
color: '#4ade80',
|
||||
fontSize: '0.9rem',
|
||||
fontSize: '0.85rem',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
<Check size={18} />
|
||||
<span>Passkey created successfully! Redirecting...</span>
|
||||
<Check size={16} />
|
||||
<span>Passkey created successfully!</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.85rem' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.82rem', fontWeight: 600, color: 'var(--mp-text, #f8fafc)', marginBottom: '0.4rem' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.8rem', fontWeight: 600, color: 'var(--mp-text, #f8fafc)', marginBottom: '0.35rem' }}>
|
||||
Device / Passkey Name (Optional)
|
||||
</label>
|
||||
<input
|
||||
@ -372,12 +427,12 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
placeholder="e.g. MacBook Pro Touch ID, Windows PC, or YubiKey"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem 1rem',
|
||||
padding: compact ? '0.6rem 0.85rem' : '0.75rem 1rem',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid var(--mp-border, rgba(255,255,255,0.12))',
|
||||
borderRadius: 8,
|
||||
color: '#fff',
|
||||
fontSize: '0.9rem',
|
||||
fontSize: compact ? '0.85rem' : '0.9rem',
|
||||
boxSizing: 'border-box',
|
||||
outline: 'none',
|
||||
}}
|
||||
@ -389,23 +444,24 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
disabled={passkeyLoading}
|
||||
onClick={handleRegisterPasskey}
|
||||
style={{
|
||||
padding: '0.85rem',
|
||||
padding: compact ? '0.65rem' : '0.85rem',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.5rem',
|
||||
fontSize: compact ? '0.85rem' : '0.9rem',
|
||||
cursor: passkeyLoading ? 'not-allowed' : 'pointer',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
{passkeyLoading ? (
|
||||
<>
|
||||
<Loader2 size={18} className="animate-spin" /> Setting up Passkey...
|
||||
<Loader2 size={16} className="animate-spin" /> Setting up Passkey...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Fingerprint size={18} /> Create & Register Passkey
|
||||
<Fingerprint size={16} /> Create & Register Passkey
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@ -418,7 +474,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
{/* ── Box 2: Authenticator App (TOTP 2FA) ── */}
|
||||
<div style={{
|
||||
border: expandedBox === 'totp' ? '1px solid var(--mp-accent, #f97316)' : '1px solid var(--mp-border, rgba(255,255,255,0.08))',
|
||||
borderRadius: 14,
|
||||
borderRadius: compact ? 10 : 14,
|
||||
background: expandedBox === 'totp' ? 'rgba(249, 115, 22, 0.03)' : 'rgba(255,255,255,0.02)',
|
||||
transition: 'all 0.2s ease',
|
||||
overflow: 'hidden',
|
||||
@ -428,7 +484,7 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
onClick={() => toggleBox('totp')}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '1.25rem 1.5rem',
|
||||
padding: compact ? '0.75rem 0.9rem' : '1.25rem 1.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
@ -438,11 +494,11 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: compact ? '0.75rem' : '1rem' }}>
|
||||
<div style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 12,
|
||||
width: compact ? 36 : 44,
|
||||
height: compact ? 36 : 44,
|
||||
borderRadius: compact ? 8 : 12,
|
||||
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(59, 130, 246, 0.05))',
|
||||
border: '1px solid rgba(59, 130, 246, 0.3)',
|
||||
color: '#60a5fa',
|
||||
@ -451,83 +507,86 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Smartphone size={24} />
|
||||
<Smartphone size={compact ? 20 : 24} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', marginBottom: '0.2rem' }}>
|
||||
<span style={{ fontWeight: 700, fontSize: '1.05rem', color: 'var(--mp-text, #f8fafc)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.15rem' }}>
|
||||
<span style={{ fontWeight: 700, fontSize: compact ? '0.92rem' : '1.05rem', color: 'var(--mp-text, #f8fafc)' }}>
|
||||
Authenticator App (TOTP 2FA)
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ margin: 0, fontSize: '0.85rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
<p style={{ margin: 0, fontSize: compact ? '0.75rem' : '0.85rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
Use Google Authenticator, Microsoft Authenticator, Authy, or 1Password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ color: 'var(--mp-muted, #94a3b8)', marginLeft: '1rem' }}>
|
||||
{expandedBox === 'totp' ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
|
||||
<div style={{ color: 'var(--mp-muted, #94a3b8)', marginLeft: '0.5rem' }}>
|
||||
{expandedBox === 'totp' ? <ChevronUp size={compact ? 18 : 20} /> : <ChevronDown size={compact ? 18 : 20} />}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* TOTP Expanded Content */}
|
||||
{expandedBox === 'totp' && (
|
||||
<div style={{
|
||||
padding: '0 1.5rem 1.5rem 1.5rem',
|
||||
padding: compact ? '0 0.9rem 0.9rem 0.9rem' : '0 1.5rem 1.5rem 1.5rem',
|
||||
borderTop: '1px solid rgba(255,255,255,0.06)',
|
||||
paddingTop: '1.25rem',
|
||||
paddingTop: compact ? '0.85rem' : '1.25rem',
|
||||
}}>
|
||||
{totpLoading ? (
|
||||
<div style={{ textAlign: 'center', padding: '2rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
<Loader2 size={24} className="animate-spin" style={{ margin: '0 auto 0.5rem auto' }} />
|
||||
<p style={{ margin: 0 }}>Generating 2FA Secret & QR Code...</p>
|
||||
<div style={{ textAlign: 'center', padding: '1.5rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
<Loader2 size={22} className="animate-spin" style={{ margin: '0 auto 0.5rem auto' }} />
|
||||
<p style={{ margin: 0, fontSize: '0.85rem' }}>Generating 2FA Secret & QR Code...</p>
|
||||
</div>
|
||||
) : totpSuccess ? (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.85rem 1rem',
|
||||
padding: '0.75rem 1rem',
|
||||
background: 'rgba(34, 197, 94, 0.12)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
borderRadius: 8,
|
||||
color: '#4ade80',
|
||||
fontSize: '0.9rem',
|
||||
fontSize: '0.85rem',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
<Check size={18} />
|
||||
<span>Two-Factor Authentication enabled successfully! Redirecting...</span>
|
||||
<Check size={16} />
|
||||
<span>Two-Factor Authentication enabled successfully!</span>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1.5rem', alignItems: 'center', marginBottom: '1.5rem' }}>
|
||||
{totpQr && (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: compact ? '1rem' : '1.5rem', alignItems: 'center', marginBottom: '1rem' }}>
|
||||
{totpUri && (
|
||||
<div style={{
|
||||
padding: '0.5rem',
|
||||
background: '#fff',
|
||||
borderRadius: 12,
|
||||
borderRadius: 10,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<img src={totpQr} alt="2FA QR Code" style={{ width: 140, height: 140, display: 'block' }} />
|
||||
<QRCodeSVG value={totpUri} size={compact ? 115 : 135} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<p style={{ margin: '0 0 0.5rem 0', fontSize: '0.85rem', color: 'var(--mp-text, #f8fafc)', fontWeight: 600 }}>
|
||||
1. Scan this QR Code with your Authenticator App
|
||||
<div style={{ flex: 1, minWidth: compact ? 180 : 200 }}>
|
||||
<p style={{ margin: '0 0 0.35rem 0', fontSize: compact ? '0.8rem' : '0.85rem', color: 'var(--mp-text, #f8fafc)', fontWeight: 600 }}>
|
||||
1. Scan QR Code with Authenticator App
|
||||
</p>
|
||||
<p style={{ margin: '0 0 0.8rem 0', fontSize: '0.8rem', color: 'var(--mp-muted, #94a3b8)' }}>
|
||||
Or manually copy the setup key below if you cannot scan the code:
|
||||
<p style={{ margin: '0 0 0.5rem 0', fontSize: '0.75rem', color: 'var(--mp-muted, #94a3b8)', lineHeight: 1.35 }}>
|
||||
Or manually copy the setup key below:
|
||||
</p>
|
||||
{totpSecret && (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
gap: '0.4rem',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
padding: '0.45rem 0.75rem',
|
||||
borderRadius: 8,
|
||||
padding: '0.35rem 0.6rem',
|
||||
borderRadius: 6,
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
}}>
|
||||
<code style={{ fontSize: '0.85rem', color: '#f97316', flex: 1, letterSpacing: '0.05em' }}>
|
||||
<code style={{ fontSize: compact ? '0.75rem' : '0.82rem', color: '#f97316', flex: 1, letterSpacing: '0.05em', wordBreak: 'break-all' }}>
|
||||
{totpSecret}
|
||||
</code>
|
||||
<button
|
||||
@ -541,19 +600,38 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
padding: '0.2rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
title="Copy Secret"
|
||||
>
|
||||
{copiedSecret ? <Check size={16} /> : <Copy size={16} />}
|
||||
{copiedSecret ? <Check size={14} /> : <Copy size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleVerifyTotp} style={{ display: 'flex', flexDirection: 'column', gap: '0.85rem' }}>
|
||||
{totpError && (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.6rem 0.8rem',
|
||||
background: 'rgba(239, 68, 68, 0.1)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.25)',
|
||||
borderRadius: 8,
|
||||
color: '#f87171',
|
||||
fontSize: '0.82rem',
|
||||
marginBottom: '0.75rem',
|
||||
}}>
|
||||
<AlertCircle size={15} />
|
||||
<span>{totpError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleVerifyTotp} style={{ display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.82rem', fontWeight: 600, color: 'var(--mp-text, #f8fafc)', marginBottom: '0.4rem' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.8rem', fontWeight: 600, color: 'var(--mp-text, #f8fafc)', marginBottom: '0.35rem' }}>
|
||||
2. Enter the 6-Digit Code from the App
|
||||
</label>
|
||||
<input
|
||||
@ -564,21 +642,23 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
setTotpError(null);
|
||||
setTotpCode(e.target.value.replace(/\D/g, ''));
|
||||
}}
|
||||
placeholder="123456"
|
||||
placeholder="000 000"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem 1rem',
|
||||
padding: compact ? '0.6rem 0.85rem' : '0.75rem 1rem',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: totpError ? '1px solid #ef4444' : '1px solid var(--mp-border, rgba(255,255,255,0.12))',
|
||||
boxShadow: totpError ? '0 0 0 3px rgba(239, 68, 68, 0.2)' : undefined,
|
||||
borderRadius: 8,
|
||||
color: '#fff',
|
||||
fontSize: '1.1rem',
|
||||
fontSize: compact ? '1rem' : '1.1rem',
|
||||
letterSpacing: '0.2em',
|
||||
textAlign: 'center',
|
||||
boxSizing: 'border-box',
|
||||
outline: 'none',
|
||||
}}
|
||||
autoComplete="one-time-code"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@ -586,12 +666,13 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
className="mp-btn mp-btn-primary"
|
||||
disabled={totpVerifying || (totpCode.trim().length !== 6 && !totpError)}
|
||||
style={{
|
||||
padding: '0.85rem',
|
||||
padding: compact ? '0.65rem' : '0.85rem',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.5rem',
|
||||
fontSize: compact ? '0.85rem' : '0.9rem',
|
||||
cursor: totpVerifying ? 'not-allowed' : 'pointer',
|
||||
borderRadius: 8,
|
||||
backgroundColor: totpError ? '#ef4444' : undefined,
|
||||
@ -602,15 +683,15 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
>
|
||||
{totpVerifying ? (
|
||||
<>
|
||||
<Loader2 size={18} className="animate-spin" /> Verifying Code...
|
||||
<Loader2 size={16} className="animate-spin" /> Verifying Code...
|
||||
</>
|
||||
) : totpError ? (
|
||||
<>
|
||||
<AlertCircle size={18} /> {totpError} — Try Again
|
||||
<AlertCircle size={16} /> Try Again
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ShieldCheck size={18} /> Verify & Enable 2FA
|
||||
<ShieldCheck size={16} /> Verify & Enable 2FA
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@ -623,58 +704,60 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Footer / Skip Actions */}
|
||||
<div style={{
|
||||
marginTop: '1.75rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: canSkip ? 'space-between' : 'center',
|
||||
paddingTop: '1rem',
|
||||
borderTop: '1px solid rgba(255,255,255,0.06)',
|
||||
}}>
|
||||
{canSkip ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSkip}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--mp-muted, #94a3b8)',
|
||||
fontSize: '0.9rem',
|
||||
cursor: 'pointer',
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: 6,
|
||||
transition: 'color 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--mp-muted, #94a3b8)')}
|
||||
>
|
||||
Skip for now →
|
||||
</button>
|
||||
{hasSecureAuth && (
|
||||
{!compact && (
|
||||
<div style={{
|
||||
marginTop: '1.75rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: canSkip ? 'space-between' : 'center',
|
||||
paddingTop: '1rem',
|
||||
borderTop: '1px solid rgba(255,255,255,0.06)',
|
||||
}}>
|
||||
{canSkip ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSkip}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--mp-muted, #94a3b8)',
|
||||
fontSize: '0.9rem',
|
||||
cursor: 'pointer',
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: 6,
|
||||
transition: 'color 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--mp-muted, #94a3b8)')}
|
||||
>
|
||||
Skip for now →
|
||||
</button>
|
||||
{hasSecureAuth && (
|
||||
<button
|
||||
type="button"
|
||||
className="mp-btn mp-btn-primary"
|
||||
onClick={onComplete}
|
||||
style={{ padding: '0.6rem 1.25rem', fontSize: '0.9rem', fontWeight: 600 }}
|
||||
>
|
||||
Continue →
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
hasSecureAuth && (
|
||||
<button
|
||||
type="button"
|
||||
className="mp-btn mp-btn-primary"
|
||||
onClick={onComplete}
|
||||
style={{ padding: '0.6rem 1.25rem', fontSize: '0.9rem', fontWeight: 600 }}
|
||||
style={{ width: '100%', padding: '0.85rem', fontSize: '0.95rem', fontWeight: 600 }}
|
||||
>
|
||||
Continue →
|
||||
Continue to Upload →
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
hasSecureAuth && (
|
||||
<button
|
||||
type="button"
|
||||
className="mp-btn mp-btn-primary"
|
||||
onClick={onComplete}
|
||||
style={{ width: '100%', padding: '0.85rem', fontSize: '0.95rem', fontWeight: 600 }}
|
||||
>
|
||||
Continue to Upload →
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<RecoveryCodesModal
|
||||
isOpen={showRecoveryModal}
|
||||
@ -687,3 +770,4 @@ export const SecuritySetupCards: React.FC<SecuritySetupCardsProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { useMemo, useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAnalytics } from './useAnalytics';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Settings, Plus, ExternalLink, Sparkles, Search, BarChart2, Package, CheckCircle, X, AlertTriangle } from 'lucide-react';
|
||||
import { Settings, Plus, ExternalLink, Search, BarChart2, Package, CheckCircle, X, AlertTriangle } from 'lucide-react';
|
||||
import api from '../../api';
|
||||
import { useAuth } from '../../App';
|
||||
import DeveloperOnboardingModal from '../../components/DeveloperOnboardingModal';
|
||||
@ -13,11 +13,10 @@ const PLUGIN_CATEGORIES = ['Admin Tools', 'Economy', 'Fun', 'World Management',
|
||||
// --- Main Component ---
|
||||
const DashboardPlugins = () => {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAuth();
|
||||
const { user, refreshUser } = useAuth();
|
||||
const { timeSeries } = useAnalytics();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [isDevModalOpen, setIsDevModalOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filterType, setFilterType] = useState<string>('all');
|
||||
const [incomingTransfers, setIncomingTransfers] = useState<any[]>([]);
|
||||
@ -169,34 +168,11 @@ const DashboardPlugins = () => {
|
||||
|
||||
if (!user?.is_developer) {
|
||||
return (
|
||||
<div className="dev-plugins-container">
|
||||
<div className="dev-plugins-card" style={{ textAlign: 'center', padding: '3.5rem 2rem' }}>
|
||||
<div style={{
|
||||
width: 64, height: 64, borderRadius: '50%',
|
||||
background: 'rgba(249, 115, 22, 0.12)', border: '1px solid rgba(249, 115, 22, 0.3)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 1.25rem'
|
||||
}}>
|
||||
<Sparkles size={32} color="#f97316" />
|
||||
</div>
|
||||
<h2 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0 0 0.5rem' }}>Developer Onboarding Required</h2>
|
||||
<p style={{ color: 'var(--dash-text-muted)', maxWidth: '520px', margin: '0 auto 1.75rem', lineHeight: 1.6 }}>
|
||||
To manage existing plugins or publish new ones, please complete the free <strong>Developer Onboarding</strong> wizard to set up your creator & legal profile.
|
||||
</p>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ padding: '0.75rem 1.75rem', fontSize: '1rem' }}
|
||||
onClick={() => setIsDevModalOpen(true)}
|
||||
>
|
||||
<Sparkles size={18} style={{ marginRight: '8px' }} />
|
||||
Complete Free Developer Onboarding
|
||||
</button>
|
||||
|
||||
<DeveloperOnboardingModal
|
||||
isOpen={isDevModalOpen}
|
||||
onClose={() => setIsDevModalOpen(false)}
|
||||
onSuccess={() => setIsDevModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
<div className="dev-plugins-container" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '60vh', padding: '2rem 1rem' }}>
|
||||
<DeveloperOnboardingModal
|
||||
embedded={true}
|
||||
onSuccess={() => refreshUser?.()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user