fix: Folder limit cannot be empty #97

This commit is contained in:
rustmailer
2026-01-05 21:32:00 +08:00
parent 0bf2003670
commit 55e97510c4
7 changed files with 29 additions and 10 deletions

View File

@@ -421,6 +421,12 @@ impl AccountV3 {
new.folder_limit = Some(folder_limit); new.folder_limit = Some(folder_limit);
} }
if let Some(clear_folder_limit) = request.clear_folder_limit {
if clear_folder_limit {
new.folder_limit = None;
}
}
if let Some(name) = &request.name { if let Some(name) = &request.name {
if name.trim().is_empty() { if name.trim().is_empty() {
new.name = None; new.name = None;

View File

@@ -127,6 +127,7 @@ pub struct AccountUpdateRequest {
/// otherwise sync up to `n` most recent emails (min 10). /// otherwise sync up to `n` most recent emails (min 10).
#[oai(validator(minimum(value = "100")))] #[oai(validator(minimum(value = "100")))]
pub folder_limit: Option<u32>, pub folder_limit: Option<u32>,
pub clear_folder_limit: Option<bool>,
/// Configuration for selective folder (mailbox/label) synchronization /// Configuration for selective folder (mailbox/label) synchronization
/// ///
/// - For IMAP/SMTP accounts: /// - For IMAP/SMTP accounts:
@@ -166,6 +167,13 @@ impl AccountUpdateRequest {
)); ));
} }
if self.clear_folder_limit == Some(true) && self.folder_limit.is_some() {
return Err(raise_error!(
"clear_folder_limit cannot be combined with folder_limit".into(),
ErrorCode::InvalidParameter
));
}
if self.clear_date_range == Some(true) if self.clear_date_range == Some(true)
&& (self.date_since.is_some() || self.date_before.is_some()) && (self.date_since.is_some() || self.date_before.is_some())
{ {

View File

@@ -128,6 +128,7 @@ const getAccountSchema = (isEdit: boolean, t: (key: string) => string) =>
.number({ invalid_type_error: t('validation.folderLimitMustBeNumber') }) .number({ invalid_type_error: t('validation.folderLimitMustBeNumber') })
.int() .int()
.min(100, { message: t('validation.folderLimitMustBeAtLeast100') }) .min(100, { message: t('validation.folderLimitMustBeAtLeast100') })
.nullable()
.optional(), .optional(),
sync_interval_min: z.number({ invalid_type_error: t('validation.incrementalSyncMustBeNumber') }).int().min(10, { message: t('validation.incrementalSyncMustBeAtLeast10') }), sync_interval_min: z.number({ invalid_type_error: t('validation.incrementalSyncMustBeNumber') }).int().min(10, { message: t('validation.incrementalSyncMustBeAtLeast10') }),
sync_batch_size: z sync_batch_size: z
@@ -221,7 +222,7 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
const accountSchema = getAccountSchema(isEdit, t); const accountSchema = getAccountSchema(isEdit, t);
const form = useForm<Account>({ const form = useForm<Account>({
mode: "all", mode: "onChange",
defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues, defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues,
resolver: zodResolver(accountSchema), resolver: zodResolver(accountSchema),
}); });
@@ -291,9 +292,11 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
}; };
if (isEdit) { if (isEdit) {
const isAllMode = !data.date_since && !data.date_before; const isAllMode = !data.date_since && !data.date_before;
const clear_folder_limit = !data.folder_limit;
updateMutation.mutate({ updateMutation.mutate({
...commonData, ...commonData,
...(isAllMode ? { clear_date_range: true } : {}) ...(isAllMode ? { clear_date_range: true } : {}),
...(clear_folder_limit ? { clear_folder_limit: true } : {})
}); });
} else { } else {
createMutation.mutate({ ...commonData, account_type: "IMAP" }); createMutation.mutate({ ...commonData, account_type: "IMAP" });

View File

@@ -116,11 +116,11 @@ export function useColumns(): ColumnDef<AccountModel>[] {
cell: ({ row }) => { cell: ({ row }) => {
const { created_user_name, created_user_email } = row.original; const { created_user_name, created_user_email } = row.original;
return ( return (
<div className="flex flex-col py-1 text-center"> <div className="flex flex-col items-center leading-[1.1]">
<span className="text-sm font-medium text-foreground"> <span className="text-[13px] font-medium text-foreground leading-none">
{created_user_name} {created_user_name}
</span> </span>
<span className="text-[11px] text-muted-foreground font-mono"> <span className="text-[11px] text-muted-foreground font-mono leading-none">
{created_user_email} {created_user_email}
</span> </span>
</div> </div>

View File

@@ -82,7 +82,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) {
const { toast } = useToast(); const { toast } = useToast();
const form = useForm<NoSyncAccount>({ const form = useForm<NoSyncAccount>({
mode: "all", mode: "onChange",
defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues, defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues,
resolver: zodResolver(accountSchema(t)), resolver: zodResolver(accountSchema(t)),
}); });

View File

@@ -178,7 +178,6 @@ export default function Step3() {
onSelect={(date) => field.onChange(date?.toLocaleDateString('en-CA'))} onSelect={(date) => field.onChange(date?.toLocaleDateString('en-CA'))}
disabled={(date) => date > new Date() || date < new Date("1900-01-01")} disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
locale={dateLocale} locale={dateLocale}
initialFocus
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
@@ -244,8 +243,11 @@ export default function Step3() {
<Input <Input
type="number" type="number"
placeholder={t('accounts.folderLimitPlaceholder')} placeholder={t('accounts.folderLimitPlaceholder')}
{...field} value={field.value ?? ''}
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value, 10) : undefined)} onChange={(e) => {
const value = e.target.value;
field.onChange(value === '' ? null : Number(value));
}}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />

View File

@@ -148,7 +148,7 @@ export function Oauth2Table({ columns, data }: DataTableProps) {
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
<DataTablePagination table={table} showSelected={true} showPageSizeSelector={true} /> <DataTablePagination table={table} showPageSizeSelector={true} />
</div> </div>
) )
} }