// // 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 . import { z } from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { toast } from '@/hooks/use-toast' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { AxiosError } from 'axios' import { ToastAction } from '@/components/ui/toast' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Loader2 } from 'lucide-react' import { add_proxy, update_proxy } from '@/api/system/api' import { useTranslation } from 'react-i18next' import { Proxy } from '@/api/system/api' const proxyFormSchema = z.object({ url: z.string() .min(1, "Proxy address cannot be empty") .superRefine((value, ctx) => { if (value.length === 0) { return; } let url: URL; try { url = new URL(value); } catch (e) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid URL format", path: [], }); return; } if (url.protocol !== 'socks5:' && url.protocol !== 'http:') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "URL must start with http:// or socks5://", path: [], }); } if (!/^[a-zA-Z0-9\-\.]+$/.test(url.hostname)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Hostname contains invalid characters", path: [], }); } const port = parseInt(url.port || '1080'); if (port <= 0 || port > 65535) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Port must be between 1-65535", path: [], }); } if (url.username && !url.password) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Password cannot be empty when username is provided", path: [], }); } else if (url.password && url.password.length < 8) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Password must be at least 8 characters", path: [], }); } }) }); export type ProxyForm = z.infer; interface Props { currentRow?: Proxy open: boolean onOpenChange: (open: boolean) => void } const defaultValues = { url: "" }; const mapCurrentRowToFormValues = (currentRow: Proxy) => { let data = { url: currentRow.url }; return data; }; export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) { const { t } = useTranslation() const isEdit = !!currentRow const queryClient = useQueryClient(); const form = useForm({ resolver: zodResolver(proxyFormSchema), defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues, }); const createMutation = useMutation({ mutationFn: add_proxy, onSuccess: handleSuccess, onError: handleError }); const updateMutation = useMutation({ mutationFn: (url: string) => update_proxy(currentRow?.id!, url), onSuccess: handleSuccess, onError: handleError }) function handleSuccess() { toast({ title: `${t('settings.proxy')} ${isEdit ? t('common.update') : t('common.add')}`, description: t('settings.yourProxyHasBeenSuccessfully', { action: isEdit ? t('common.update').toLowerCase() : t('common.add').toLowerCase() }), action: {t('common.close')}, }); queryClient.invalidateQueries({ queryKey: ['proxy-list'] }); form.reset(); onOpenChange(false); } function handleError(error: AxiosError) { const errorMessage = (error.response?.data as { message?: string })?.message || error.message || t('settings.proxyUpdateOrAddFailed', { action: isEdit ? t('common.update') : t('common.add') }); toast({ variant: "destructive", title: `${t('settings.proxy')} ${isEdit ? t('common.update') : t('common.add')} ${t('common.error')}`, description: errorMessage as string, action: {t('common.tryAgain')}, }); } const onSubmit = (values: ProxyForm) => { const url = values.url; if (isEdit) { updateMutation.mutate(url); } else { createMutation.mutate(url); } } return ( { form.reset() onOpenChange(state) }} > {isEdit ? t('settings.edit') + ' ' + t('settings.proxy') : t('common.add') + ' ' + t('settings.proxy')} {isEdit ? t('settings.updateTheProxyHere') : t('settings.addNewProxyHere')} {t('accounts.clickSaveWhenDone')}
( {t('settings.proxy')} URL {t('settings.pleaseUseAnIpAddressForBetterReliability')} )} />
) }