mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
fix: make email/login_name immutable and add ui sortable account_name #195
This commit is contained in:
@@ -22,9 +22,10 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Tabs, TabsContent } from '@/components/ui/tabs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AccountModel } from '@/api/account/api'
|
||||
import useProxyList from '@/hooks/use-proxy'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
@@ -34,9 +35,7 @@ interface Props {
|
||||
|
||||
export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
||||
|
||||
const { getUrlById } = useProxyList();
|
||||
|
||||
const sinceText = (() => {
|
||||
if (currentRow.date_since?.fixed) {
|
||||
@@ -56,8 +55,6 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
const hasSince = !!currentRow.date_since;
|
||||
const hasBefore = !!currentRow.date_before?.value;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
@@ -71,13 +68,8 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[35rem] w-full pr-4 -mr-4 py-1">
|
||||
<Tabs defaultValue="account" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-1">
|
||||
<TabsTrigger value="account">{t('accounts.accountDetails')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="account">
|
||||
<div className="mt-4 space-y-6">
|
||||
{/* Account Details Card */}
|
||||
<Card>
|
||||
<CardContent className="mt-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -91,19 +83,19 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.name')}:</span>
|
||||
<span>{currentRow.name ?? t('accounts.notAvailable')}</span>
|
||||
<span>{currentRow.login_name ?? t('accounts.notAvailable')}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.enabled')}:</span>
|
||||
<Checkbox checked={currentRow.enabled} disabled />
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.incrementalSyncInterval')}:</span>
|
||||
<span>{t('accounts.everyMinutes', { minutes: currentRow.sync_interval_min })}</span>
|
||||
<span className="text-muted-foreground">{t('accounts.downloadInterval')}:</span>
|
||||
<span>{t('accounts.everyMinutes', { minutes: currentRow.download_interval_min })}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.syncBatchSize')}:</span>
|
||||
<span>{currentRow.sync_batch_size}</span>
|
||||
<span className="text-muted-foreground">{t('accounts.downloadBatchSize')}:</span>
|
||||
<span>{currentRow.download_batch_size}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.capabilities')}:</span>
|
||||
@@ -112,7 +104,7 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.syncScope')}:</span>
|
||||
<span className="text-muted-foreground">{t('accounts.downloadScope')}:</span>
|
||||
{hasSince && (
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -135,8 +127,8 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</div>
|
||||
)}
|
||||
{!hasSince && !hasBefore && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t('accounts.syncAll')}
|
||||
<span className="text-sm">
|
||||
{t('accounts.downloadAll')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -183,7 +175,15 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">{t('accounts.useProxyField')}:</span>
|
||||
<span>{currentRow.imap?.use_proxy ? "true" : "false"}</span>
|
||||
<span>
|
||||
{(() => {
|
||||
if (!currentRow.imap?.use_proxy) {
|
||||
return t('accounts.useNoProxy');
|
||||
}
|
||||
const proxyUrl = getUrlById(currentRow.imap?.use_proxy);
|
||||
return proxyUrl || `${t('common.yes')} (${currentRow.imap?.use_proxy})`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -193,14 +193,14 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
<CardTitle>{t('accounts.selectedMailboxes')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{currentRow.sync_folders?.length ? (
|
||||
{currentRow.download_folders?.length ? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm mt-2 text-muted-foreground">
|
||||
{t('accounts.foldersConfiguredForSync', { count: currentRow.sync_folders.length })}
|
||||
{t('accounts.foldersConfiguredForSync', { count: currentRow.download_folders.length })}
|
||||
</div>
|
||||
<ScrollArea className="h-[300px] rounded-md border">
|
||||
<div className="p-2">
|
||||
{currentRow.sync_folders.map((folder, index) => (
|
||||
{currentRow.download_folders.map((folder, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center py-2 px-3 hover:bg-accent rounded-md transition-colors"
|
||||
|
||||
@@ -85,7 +85,8 @@ const getDateSelectionSchema = (t: (key: string) => string) => z.union([
|
||||
]);
|
||||
|
||||
export type Account = {
|
||||
name?: string;
|
||||
login_name?: string;
|
||||
account_name?: string;
|
||||
email: string;
|
||||
imap: {
|
||||
host: string;
|
||||
@@ -111,13 +112,14 @@ export type Account = {
|
||||
value?: number;
|
||||
};
|
||||
folder_limit?: number;
|
||||
sync_interval_min: number;
|
||||
sync_batch_size: number;
|
||||
download_interval_min: number;
|
||||
download_batch_size: number;
|
||||
};
|
||||
|
||||
const getAccountSchema = (isEdit: boolean, t: (key: string) => string) =>
|
||||
z.object({
|
||||
name: z.string().optional(),
|
||||
account_name: z.string().optional(),
|
||||
login_name: z.string().optional(),
|
||||
email: z.string({ required_error: t('validation.emailRequired') }).email({ message: t('validation.invalidEmail') }),
|
||||
imap: getImapConfigSchema(isEdit, t),
|
||||
enabled: z.boolean(),
|
||||
@@ -130,8 +132,8 @@ const getAccountSchema = (isEdit: boolean, t: (key: string) => string) =>
|
||||
.min(100, { message: t('validation.folderLimitMustBeAtLeast100') })
|
||||
.nullable()
|
||||
.optional(),
|
||||
sync_interval_min: z.number({ invalid_type_error: t('validation.incrementalSyncMustBeNumber') }).int().min(10, { message: t('validation.incrementalSyncMustBeAtLeast10') }),
|
||||
sync_batch_size: z
|
||||
download_interval_min: z.number({ invalid_type_error: t('validation.incrementalSyncMustBeNumber') }).int().min(10, { message: t('validation.incrementalSyncMustBeAtLeast10') }),
|
||||
download_batch_size: z
|
||||
.number({ invalid_type_error: t('validation.singleRequestBatchSizeMustBeNumber') })
|
||||
.int()
|
||||
.min(10, { message: t('validation.singleRequestBatchSizeTooSmall') })
|
||||
@@ -147,9 +149,9 @@ type Step = {
|
||||
export type Steps = [...Step[]];
|
||||
|
||||
const getSteps = (t: (key: string) => string): Steps => [
|
||||
{ id: "step-1", name: t('accounts.steps.emailAddress'), fields: ["email"] },
|
||||
{ id: "step-2", name: t('accounts.steps.imap'), fields: ["imap", "use_dangerous", "name"] },
|
||||
{ id: "step-3", name: t('accounts.steps.syncPreferences'), fields: ["enabled", "date_since", "date_before", "folder_limit", "sync_interval_min", "sync_batch_size"] },
|
||||
{ id: "step-1", name: t('accounts.steps.emailAddress'), fields: ["email", "account_name"] },
|
||||
{ id: "step-2", name: t('accounts.steps.imap'), fields: ["imap", "use_dangerous", "login_name"] },
|
||||
{ id: "step-3", name: t('accounts.steps.syncPreferences'), fields: ["enabled", "date_since", "date_before", "folder_limit", "download_interval_min", "download_batch_size"] },
|
||||
{ id: "step-4", name: t('accounts.steps.summary'), fields: [] },
|
||||
];
|
||||
|
||||
@@ -162,7 +164,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const defaultValues: Account = {
|
||||
name: undefined,
|
||||
login_name: undefined,
|
||||
account_name: undefined,
|
||||
email: '',
|
||||
imap: {
|
||||
host: "",
|
||||
@@ -179,8 +182,8 @@ const defaultValues: Account = {
|
||||
date_since: undefined,
|
||||
date_before: undefined,
|
||||
folder_limit: undefined,
|
||||
sync_interval_min: 10,
|
||||
sync_batch_size: 30,
|
||||
download_interval_min: 10,
|
||||
download_batch_size: 30,
|
||||
};
|
||||
|
||||
const emptyImap: ImapConfig = {
|
||||
@@ -199,7 +202,7 @@ const mapCurrentRowToFormValues = (currentRow: AccountModel): Account => {
|
||||
}
|
||||
|
||||
return {
|
||||
name: currentRow.name ?? undefined,
|
||||
login_name: currentRow.login_name ?? undefined,
|
||||
email: currentRow.email,
|
||||
imap,
|
||||
enabled: currentRow.enabled,
|
||||
@@ -207,8 +210,8 @@ const mapCurrentRowToFormValues = (currentRow: AccountModel): Account => {
|
||||
date_since: currentRow.date_since ?? undefined,
|
||||
date_before: currentRow.date_before ?? undefined,
|
||||
folder_limit: currentRow.folder_limit ?? undefined,
|
||||
sync_interval_min: currentRow.sync_interval_min ?? 10,
|
||||
sync_batch_size: currentRow.sync_batch_size ?? 50,
|
||||
download_interval_min: currentRow.download_interval_min ?? 10,
|
||||
download_batch_size: currentRow.download_batch_size ?? 30,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -272,7 +275,8 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
(data: Account) => {
|
||||
const commonData = {
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
account_name: data.account_name,
|
||||
login_name: data.login_name,
|
||||
imap: {
|
||||
...data.imap,
|
||||
auth: {
|
||||
@@ -287,8 +291,8 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
date_since: data.date_since,
|
||||
date_before: data.date_before,
|
||||
folder_limit: data.folder_limit,
|
||||
sync_interval_min: data.sync_interval_min,
|
||||
sync_batch_size: data.sync_batch_size,
|
||||
download_interval_min: data.download_interval_min,
|
||||
download_batch_size: data.download_batch_size,
|
||||
};
|
||||
if (isEdit) {
|
||||
const isAllMode = !data.date_since && !data.date_before;
|
||||
@@ -328,7 +332,7 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
}
|
||||
setAutoConfigLoading(true);
|
||||
const email = form.getValues('email');
|
||||
|
||||
form.setValue('login_name', email);
|
||||
try {
|
||||
const result = await autoconfig(email);
|
||||
if (result) {
|
||||
|
||||
@@ -42,7 +42,17 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
},
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: { className: 'max-w-[120px]' },
|
||||
meta: { className: 'max-w-[100px]' },
|
||||
},
|
||||
{
|
||||
accessorKey: "account_name",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={t('accounts.name')} className="justify-center" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return <LongText>{row.original.account_name ?? "n/a"}</LongText>
|
||||
},
|
||||
meta: { className: 'max-w-[100px]' },
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
@@ -96,7 +106,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
if (account_type === "NoSync") {
|
||||
return <LongText className="text-center">n/a</LongText>
|
||||
}
|
||||
return <LongText className="text-center">{row.original.sync_interval_min} min</LongText>
|
||||
return <LongText className="text-center">{row.original.download_interval_min} min</LongText>
|
||||
},
|
||||
meta: { className: 'text-center max-w-[120px]' },
|
||||
enableHiding: false,
|
||||
|
||||
@@ -182,10 +182,10 @@ export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props)
|
||||
const itemsWithChildren = getParentIds(tree);
|
||||
setItemsWithChildren(itemsWithChildren);
|
||||
setExpandedItems(itemsWithChildren);
|
||||
const sync_folders = data
|
||||
.filter(mailbox => currentRow.sync_folders.includes(mailbox.name))
|
||||
const download_folders = data
|
||||
.filter(mailbox => currentRow.download_folders.includes(mailbox.name))
|
||||
.map(mailbox => mailbox.id.toString());
|
||||
setSelectedItems(sync_folders);
|
||||
setSelectedItems(download_folders);
|
||||
setError(undefined);
|
||||
}
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -38,14 +38,14 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
const accountSchema = (t: (key: string) => string) =>
|
||||
z.object({
|
||||
name: z.string().optional(),
|
||||
account_name: z.string().optional(),
|
||||
email: z.string({ required_error: t('validation.emailRequired') }).email({ message: t('validation.invalidEmail') }),
|
||||
enabled: z.boolean()
|
||||
});
|
||||
|
||||
|
||||
export type NoSyncAccount = {
|
||||
name?: string;
|
||||
account_name?: string;
|
||||
email: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
@@ -60,7 +60,7 @@ interface Props {
|
||||
|
||||
|
||||
const defaultValues: NoSyncAccount = {
|
||||
name: '',
|
||||
account_name: '',
|
||||
email: '',
|
||||
enabled: true
|
||||
};
|
||||
@@ -68,7 +68,7 @@ const defaultValues: NoSyncAccount = {
|
||||
|
||||
const mapCurrentRowToFormValues = (currentRow: AccountModel): NoSyncAccount => {
|
||||
let account = {
|
||||
name: currentRow.name === null ? '' : currentRow.name,
|
||||
account_name: currentRow.account_name === null ? '' : currentRow.account_name,
|
||||
email: currentRow.email,
|
||||
enabled: currentRow.enabled
|
||||
};
|
||||
@@ -132,7 +132,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
(data: NoSyncAccount) => {
|
||||
const commonData = {
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
account_name: data.account_name,
|
||||
enabled: data.enabled,
|
||||
use_dangerous: false
|
||||
};
|
||||
@@ -164,7 +164,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
{t('accounts.clickSaveWhenDone')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className='h-[13rem] w-full pr-4 -mr-4 py-1'>
|
||||
<ScrollArea className='h-[20rem] w-full pr-4 -mr-4 py-1'>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id='nosync-account-form'
|
||||
@@ -189,9 +189,9 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* <FormField
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
name="account_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center justify-between">
|
||||
@@ -204,7 +204,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='enabled'
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function Step1({ isEdit }: StepProps) {
|
||||
{t('accounts.emailAddress')}:
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('accounts.emailPlaceholder')} readOnly={isEdit} {...field} />
|
||||
<Input placeholder={t('accounts.emailPlaceholder')} disabled={isEdit} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{isEdit && (
|
||||
@@ -66,6 +66,22 @@ export default function Step1({ isEdit }: StepProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={control}
|
||||
name="account_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center justify-between">
|
||||
{t('accounts.name')}:
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('accounts.namePlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{t('accounts.optional')}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,7 @@ export default function Step2({ isEdit }: StepProps) {
|
||||
const { t } = useTranslation()
|
||||
const { control } = useFormContext<Account>();
|
||||
const { proxyOptions } = useProxyList();
|
||||
|
||||
const imapAuthMethod = useWatch({
|
||||
control,
|
||||
name: "imap.auth.auth_type",
|
||||
@@ -130,14 +131,14 @@ export default function Step2({ isEdit }: StepProps) {
|
||||
/>
|
||||
<FormField
|
||||
control={control}
|
||||
name="name"
|
||||
name="login_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center justify-between">
|
||||
{t('accounts.name')}:
|
||||
{t('accounts.login_name')}:
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('accounts.namePlaceholder')} {...field} />
|
||||
<Input placeholder={t('accounts.namePlaceholder')} {...field} disabled={isEdit} />
|
||||
</FormControl>
|
||||
<FormDescription>{t('accounts.nameDescription')}</FormDescription>
|
||||
<FormMessage />
|
||||
@@ -198,7 +199,9 @@ export default function Step2({ isEdit }: StepProps) {
|
||||
<FormLabel className="flex items-center justify-between">{t('accounts.useProxy')} ({t('accounts.optional')}):</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
onValueChange={(val) => field.onChange(Number(val))}
|
||||
onValueChange={(val) => {
|
||||
field.onChange(val === 'none' ? undefined : Number(val))
|
||||
}}
|
||||
defaultValue={field.value?.toString()}
|
||||
>
|
||||
<FormControl>
|
||||
@@ -207,14 +210,15 @@ export default function Step2({ isEdit }: StepProps) {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{proxyOptions && proxyOptions.length > 0 ? (
|
||||
<SelectItem key="none" value="none">
|
||||
{t('accounts.useNoProxy')}
|
||||
</SelectItem>
|
||||
{proxyOptions && proxyOptions.length > 0 && (
|
||||
proxyOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value.toString()}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem disabled value="__none__">{t('settings.noProxies')}</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -82,32 +82,32 @@ export default function Step3() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormField
|
||||
control={control}
|
||||
name="sync_interval_min"
|
||||
name="download_interval_min"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('accounts.incrementalSync')}</FormLabel>
|
||||
<FormLabel>{t('accounts.downloadInterval')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} onChange={(e) => field.onChange(parseInt(e.target.value, 10))} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
{t('accounts.incrementalSyncDescription')}
|
||||
{t('accounts.downloadIntervalPlaceholder')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={control}
|
||||
name="sync_batch_size"
|
||||
name="download_batch_size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('accounts.syncBatchSize')}</FormLabel>
|
||||
<FormLabel>{t('accounts.downloadBatchSize')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} onChange={(e) => field.onChange(parseInt(e.target.value, 10))} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
{t('accounts.syncBatchSizeDescription')}
|
||||
{t('accounts.downloadBatchSizeDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -133,19 +133,19 @@ export default function Step3() {
|
||||
<hr className="my-4" />
|
||||
<div className="space-y-4">
|
||||
<FormItem>
|
||||
<FormLabel className="text-base font-semibold">{t('accounts.syncScope', 'Sync Strategy')}</FormLabel>
|
||||
<FormLabel className="text-base font-semibold">{t('accounts.downloadScope')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('accounts.syncScopeDescription', 'Choose which emails should be indexed and archived.')}
|
||||
{t('accounts.downloadScopeDescription')}
|
||||
</FormDescription>
|
||||
<Select value={syncMode} onValueChange={(v) => handleModeChange(v as SyncMode)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t('accounts.selectMode')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">{t('accounts.syncAll', 'Sync All Emails')}</SelectItem>
|
||||
<SelectItem value="since_fixed">{t('accounts.sinceFixed', 'Since Specific Date')}</SelectItem>
|
||||
<SelectItem value="since_relative">{t('accounts.sinceRelative', 'Keep Recent Emails')}</SelectItem>
|
||||
<SelectItem value="before_relative">{t('accounts.beforeRelative', 'Archive Old Emails Only')}</SelectItem>
|
||||
<SelectItem value="all">{t('accounts.downloadAll')}</SelectItem>
|
||||
<SelectItem value="since_fixed">{t('accounts.sinceFixed')}</SelectItem>
|
||||
<SelectItem value="since_relative">{t('accounts.sinceRelative')}</SelectItem>
|
||||
<SelectItem value="before_relative">{t('accounts.beforeRelative')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
@@ -21,10 +21,12 @@ import { useFormContext } from "react-hook-form";
|
||||
import { Account } from "./action-dialog";
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useProxyList from "@/hooks/use-proxy";
|
||||
|
||||
export default function Step4() {
|
||||
const { t } = useTranslation();
|
||||
const { getValues } = useFormContext<Account>();
|
||||
const { getUrlById } = useProxyList();
|
||||
const summaryData = getValues();
|
||||
|
||||
|
||||
@@ -48,15 +50,20 @@ export default function Step4() {
|
||||
|
||||
return (
|
||||
<div className="rounded-xl">
|
||||
<Accordion type="multiple" defaultValue={['email', 'name', 'imap', 'date_since', 'folder_limit', 'sync_interval', 'sync_scope', 'sync_batch_size']}>
|
||||
<Accordion type="multiple" defaultValue={['email', 'account_name', 'login_name', 'imap', 'date_since', 'folder_limit', 'sync_interval', 'sync_scope', 'sync_batch_size']}>
|
||||
<AccordionItem key="email" value="email">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.email')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.email}</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem key="name" value="name">
|
||||
<AccordionItem key="account_name" value="account_name">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.name')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.name ?? t('accounts.notAvailable')}</AccordionContent>
|
||||
<AccordionContent>{summaryData.account_name ?? t('accounts.notAvailable')}</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem key="login_name" value="login_name">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.login_name')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.login_name ?? t('accounts.notAvailable')}</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem key="imap" value="imap">
|
||||
@@ -93,7 +100,15 @@ export default function Step4() {
|
||||
)}
|
||||
<tr>
|
||||
<td className="px-6 py-2 whitespace-nowrap text-sm font-medium text-gray-600">{t('accounts.useProxyField')}:</td>
|
||||
<td className="px-6 py-2 whitespace-nowrap text-sm">{summaryData.imap.use_proxy ? t('common.yes') : t('common.no')}</td>
|
||||
<td className="px-6 py-2 whitespace-nowrap text-sm">
|
||||
{(() => {
|
||||
if (!summaryData.imap.use_proxy) {
|
||||
return t('accounts.useNoProxy');
|
||||
}
|
||||
const proxyUrl = getUrlById(summaryData.imap.use_proxy);
|
||||
return proxyUrl || `${t('common.yes')} (${summaryData.imap.use_proxy})`;
|
||||
})()}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -103,7 +118,7 @@ export default function Step4() {
|
||||
|
||||
<AccordionItem key="sync_scope" value="sync_scope">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">
|
||||
{t('accounts.syncScope')}:
|
||||
{t('accounts.downloadScope')}:
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="space-y-3">
|
||||
@@ -131,8 +146,8 @@ export default function Step4() {
|
||||
)}
|
||||
|
||||
{!hasSince && !hasBefore && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t('accounts.syncAll')}
|
||||
<span className="text-sm">
|
||||
{t('accounts.downloadAll')}
|
||||
</span>
|
||||
)}
|
||||
</AccordionContent>
|
||||
@@ -145,13 +160,13 @@ export default function Step4() {
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem key="sync_interval" value="sync_interval">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.incrementalSync')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.sync_interval_min} {t('accounts.minutes')}</AccordionContent>
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.downloadInterval')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.download_interval_min} {t('accounts.minutes')}</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem key="sync_batch_size" value="sync_batch_size">
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.syncBatchSize')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.sync_batch_size}</AccordionContent>
|
||||
<AccordionTrigger className="font-medium capitalize text-gray-600">{t('accounts.downloadBatchSize')}:</AccordionTrigger>
|
||||
<AccordionContent>{summaryData.download_batch_size}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function Accounts() {
|
||||
<FixedHeader />
|
||||
|
||||
<Main>
|
||||
<div className="mx-auto w-full max-w-[88rem] px-4">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user