dfssdfsfdsf
This commit is contained in:
2026-04-09 21:00:04 +02:00
parent 983b67e6fc
commit 89bc86b615
27 changed files with 2921 additions and 408 deletions

View File

@@ -188,6 +188,8 @@ export function Settings() {
const [showImapForm, setShowImapForm] = useState(false)
const [imapForm, setImapForm] = useState({ email: '', password: '', imapHost: 'imap.porkbun.com', imapPort: 993, imapSecure: true })
const [imapConnecting, setImapConnecting] = useState(false)
const [recoveringAccountId, setRecoveringAccountId] = useState<string | null>(null)
const [reSortingAccountId, setReSortingAccountId] = useState<string | null>(null)
const [vipSenders, setVipSenders] = useState<VIPSender[]>([])
const [newVipEmail, setNewVipEmail] = useState('')
const [subscription, setSubscription] = useState<Subscription | null>(null)
@@ -224,6 +226,7 @@ export function Settings() {
const [showNameLabelPanel, setShowNameLabelPanel] = useState(false)
const [referralData, setReferralData] = useState<{ referralCode: string; referralCount: number } | null>(null)
const [loadingReferral, setLoadingReferral] = useState(false)
const [resettingSort, setResettingSort] = useState(false)
// Control Panel Sub-Tabs
const [controlPanelTab, setControlPanelTab] = useState<'rules' | 'cleanup' | 'labels'>('rules')
@@ -612,6 +615,51 @@ export function Settings() {
}
}
const handleRecoverEmails = async (accountId: string) => {
if (!user?.$id) return
setRecoveringAccountId(accountId)
try {
const res = await api.recoverEmails(accountId)
if (res.error) {
showMessage('error', res.error.message || 'Recovery failed')
return
}
const data = res.data as { recovered?: number; message?: string } | undefined
const n = data?.recovered ?? 0
const text =
data?.message ||
(n > 0 ? `${n} emails recovered to inbox` : 'No emails found outside inbox')
showMessage('success', text)
} finally {
setRecoveringAccountId(null)
}
}
const handleReSortEmails = async (accountId: string) => {
if (!user?.$id) return
if (
!window.confirm(
'Move messages from Junk, Archive, MailFlow/EmailSorter folders (and similar sort targets) back to INBOX and remove MailFlow category tags? Then run Sort again. Sent/Drafts/Trash are not touched.',
)
) {
return
}
setReSortingAccountId(accountId)
try {
const res = await api.reSortEmails(accountId)
if (res.error) {
showMessage('error', res.error.message || 'Re-sort prep failed')
return
}
const data = res.data as
| { recovered?: number; mailFlowKeywordsStripped?: number; message?: string }
| undefined
showMessage('success', data?.message || 'Re-sort prep completed')
} finally {
setReSortingAccountId(null)
}
}
const handleConnectImap = async (e: React.FormEvent) => {
e.preventDefault()
if (!user?.$id || !imapForm.email.trim() || !imapForm.password) return
@@ -1025,10 +1073,45 @@ export function Settings() {
<p className="text-sm text-slate-500 dark:text-slate-400 capitalize">{account.provider === 'imap' ? 'IMAP' : account.provider}</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 flex-wrap justify-end">
<Badge variant={account.connected ? 'success' : 'secondary'}>
{account.connected ? 'Connected' : 'Disconnected'}
</Badge>
{account.provider === 'imap' && (
<>
<Button
type="button"
variant="outline"
size="sm"
disabled={
recoveringAccountId === account.id ||
reSortingAccountId === account.id
}
onClick={() => handleRecoverEmails(account.id)}
>
{recoveringAccountId === account.id ? (
<Loader2 className="w-4 h-4 animate-spin mr-2" />
) : null}
Recover Emails
</Button>
<Button
type="button"
variant="secondary"
size="sm"
disabled={
recoveringAccountId === account.id ||
reSortingAccountId === account.id
}
onClick={() => handleReSortEmails(account.id)}
title="Reset wrongly sorted mail: move from Junk/Archive/MailFlow folders to INBOX and clear MailFlow tags"
>
{reSortingAccountId === account.id ? (
<Loader2 className="w-4 h-4 animate-spin mr-2" />
) : null}
Re-sort all
</Button>
</>
)}
<Button variant="ghost" size="icon" onClick={() => handleDisconnectAccount(account.id)}>
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
@@ -2325,6 +2408,67 @@ export function Settings() {
{activeTab === 'name-labels' && isAdmin && (
<div className="space-y-6">
<Card className="border-amber-200 dark:border-amber-900">
<CardHeader>
<div className="flex items-center gap-2">
<Shield className="w-5 h-5 text-amber-600 dark:text-amber-400" />
<CardTitle>Reset sort data (admin)</CardTitle>
</div>
<CardDescription>
Clears email stats, digests, usage, and onboarding progress for the chosen user. Removes the{' '}
<code className="text-xs">$MailFlow-sorted</code> flag from all messages in each IMAP account&apos;s INBOX.
Does not remove email connections or subscriptions.
</CardDescription>
</CardHeader>
<CardContent>
<Button
type="button"
variant="destructive"
disabled={resettingSort}
onClick={async () => {
const email = window.prompt('User email to reset:', 'support@webklar.com')
if (email == null) return
const trimmed = email.trim()
if (!trimmed) {
showMessage('error', 'Email is required')
return
}
if (
!window.confirm(
`Reset ALL sort-related data for ${trimmed}? This cannot be undone.`,
)
) {
return
}
setResettingSort(true)
try {
const res = await api.resetSortData(trimmed)
if (res.error) {
showMessage('error', res.error.message)
return
}
const d = res.data
showMessage(
'success',
`Reset OK. Stats/digests/usage cleared; IMAP $MailFlow-sorted removed from ${d?.imapCleared ?? 0} message(s).`,
)
} finally {
setResettingSort(false)
}
}}
>
{resettingSort ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Resetting
</>
) : (
'Reset sort data'
)}
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<div className="flex items-center gap-2">