mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
222 lines
8.4 KiB
TypeScript
222 lines
8.4 KiB
TypeScript
//
|
|
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
|
|
//
|
|
// This file is part of the Bichon Email Archiving Project
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import { useState } from 'react'
|
|
import useDialogState from '@/hooks/use-dialog-state'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Main } from '@/components/layout/main'
|
|
import { AccountActionDialog } from './components/action-dialog'
|
|
import { useColumns } from './components/columns'
|
|
import { AccountDeleteDialog } from './components/delete-dialog'
|
|
import { AccountTable } from './components/table'
|
|
import AccountProvider, {
|
|
type AccountDialogType,
|
|
} from './context'
|
|
import { Plus } from 'lucide-react'
|
|
import Logo from '@/assets/logo.svg'
|
|
import { AccountDetailDrawer } from './components/account-detail'
|
|
import { AccountModel, list_accounts } from '@/api/account/api'
|
|
import { TableSkeleton } from '@/components/table-skeleton'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { OAuth2TokensDialog } from './components/oauth2-tokens'
|
|
import { RunningStateDialog } from './components/running-state-dialog'
|
|
import { FixedHeader } from '@/components/layout/fixed-header'
|
|
import { DownloadFoldersDialog } from './components/download-folders'
|
|
import { NoSyncAccountDialog } from './components/nosync-dialog'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { AccountAccessAssignmentDialog } from './components/access-assignment-dialog'
|
|
import { useCurrentUser } from '@/hooks/use-current-user'
|
|
import { AddAccountDialog } from './components/add-account-dialog'
|
|
|
|
export default function Accounts() {
|
|
const { t } = useTranslation()
|
|
const columns = useColumns()
|
|
// Dialog states
|
|
const [currentRow, setCurrentRow] = useState<AccountModel | null>(null)
|
|
const [open, setOpen] = useDialogState<AccountDialogType>(null)
|
|
const { require_any_permission } = useCurrentUser()
|
|
|
|
const { data: accountList, isLoading } = useQuery({
|
|
queryKey: ['account-list'],
|
|
queryFn: list_accounts,
|
|
refetchInterval: (query) => {
|
|
const items = (query.state.data as { items?: { deleting?: boolean }[] })?.items;
|
|
return items?.some((item) => item.deleting) ? 5000 : false;
|
|
},
|
|
})
|
|
|
|
const hasAccounts = accountList != null && accountList.items.length > 0;
|
|
|
|
return (
|
|
<AccountProvider value={{ open, setOpen, currentRow, setCurrentRow }}>
|
|
<FixedHeader />
|
|
|
|
<Main>
|
|
<div className="mx-auto w-full max-w-[108rem] px-4">
|
|
<div className='mb-2 flex items-center justify-between flex-wrap gap-x-4 gap-y-2'>
|
|
<div>
|
|
<h2 className='text-2xl font-bold tracking-tight'>{t('accounts.title')}</h2>
|
|
<p className='text-muted-foreground'>
|
|
{t('accounts.description')}
|
|
</p>
|
|
</div>
|
|
{require_any_permission(['system:root', 'account:create']) && <div className="flex gap-2">
|
|
<div className="flex rounded-md shadow-sm">
|
|
<Button
|
|
onClick={() => setOpen("add")}
|
|
className="border-r-0"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
{t('accounts.add')}
|
|
</Button>
|
|
</div>
|
|
</div>}
|
|
</div>
|
|
|
|
<div className='flex-1 overflow-auto py-1 flex-row lg:space-x-12 space-y-0'>
|
|
{isLoading ? (
|
|
<TableSkeleton columns={columns.length} rows={10} />
|
|
) : hasAccounts ? (
|
|
<AccountTable data={accountList.items} columns={columns} />
|
|
) : (
|
|
<div className="flex h-[450px] shrink-0 items-center justify-center rounded-md border border-dashed">
|
|
<div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
|
|
<img
|
|
src={Logo}
|
|
className="max-h-[100px] w-auto opacity-20 saturate-0 transition-all duration-300 hover:opacity-100 hover:saturate-100 object-contain"
|
|
alt="Bichon Logo"
|
|
/>
|
|
<h3 className="mt-4 text-lg font-semibold">{t('accounts.noAccountConfigurations')}</h3>
|
|
<p className="mb-4 mt-2 text-sm text-muted-foreground">
|
|
{t('accounts.noAccountConfigurationsDesc')}
|
|
</p>
|
|
<div className="mt-4 flex flex-col items-center gap-3 sm:flex-row sm:flex-wrap sm:justify-center sm:gap-4">
|
|
<Button variant="default" className="w-64" onClick={() => setOpen("add")}>
|
|
{t('accounts.add')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Main>
|
|
<AddAccountDialog
|
|
key='account-add'
|
|
open={open === 'add'}
|
|
onOpenChange={() => setOpen('add')} />
|
|
|
|
<AccountActionDialog
|
|
key='imap-account-add'
|
|
open={open === 'add-imap'}
|
|
onOpenChange={() => setOpen('add-imap')}
|
|
/>
|
|
|
|
<NoSyncAccountDialog
|
|
key='nosync-account-add'
|
|
open={open === 'add-nosync'}
|
|
onOpenChange={() => setOpen('add-nosync')}
|
|
/>
|
|
|
|
{currentRow && (
|
|
<>
|
|
<AccountActionDialog
|
|
key={`imap-account-edit-${currentRow.id}`}
|
|
open={open === 'edit-imap'}
|
|
onOpenChange={() => {
|
|
setOpen('edit-imap')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>
|
|
<NoSyncAccountDialog
|
|
key={`nosync-account-edit-${currentRow.id}`}
|
|
open={open === 'edit-nosync'}
|
|
onOpenChange={() => {
|
|
setOpen('edit-nosync')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>
|
|
|
|
{require_any_permission(['system:root', 'account:read_details'], currentRow.id) && <RunningStateDialog
|
|
key='running-state'
|
|
open={open === 'running-state'}
|
|
onOpenChange={() => {
|
|
setOpen('running-state')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>}
|
|
|
|
<AccountDeleteDialog
|
|
key={`account-delete-${currentRow.id}`}
|
|
open={open === 'delete'}
|
|
onOpenChange={() => {
|
|
setOpen('delete')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>
|
|
<DownloadFoldersDialog
|
|
key={`sync-folders-${currentRow.id}`}
|
|
open={open === 'sync-folders'}
|
|
onOpenChange={() => {
|
|
setOpen('sync-folders')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>
|
|
{require_any_permission(['system:root', 'account:manage'], currentRow.id) && <AccountAccessAssignmentDialog
|
|
key={`access-assign-${currentRow.id}`}
|
|
open={open === 'access-assign'}
|
|
onOpenChange={() => {
|
|
setOpen('access-assign')
|
|
setTimeout(() => {
|
|
setCurrentRow(null)
|
|
}, 500)
|
|
}}
|
|
currentRow={currentRow}
|
|
/>}
|
|
|
|
<AccountDetailDrawer
|
|
open={open === 'detail'}
|
|
onOpenChange={() => setOpen('detail')}
|
|
currentRow={currentRow}
|
|
/>
|
|
{require_any_permission(['system:root', 'account:manage'], currentRow.id) && <OAuth2TokensDialog open={open === 'oauth2'}
|
|
onOpenChange={() => setOpen('oauth2')}
|
|
currentRow={currentRow}
|
|
/>}
|
|
</>
|
|
)}
|
|
</AccountProvider>
|
|
)
|
|
}
|