import { useEffect, useState, type Dispatch, type SetStateAction } from "react" import { useNavigate } from "@tanstack/react-router" import { LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react" import { useTranslation } from "react-i18next" import { toast } from "sonner" import { AppMainCard } from "@/components/common/app-main-card" import { MainCardHeader } from "@/components/common/main-card-header" import { filterValuesBySchema } from "@/components/common/merge-plugin-schema" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Skeleton } from "@/components/ui/skeleton" import { providerStoreActions } from "@/store" import { ProviderActiveUploaderBadge, ProviderDefaultConfigBadge, } from "./provider-status-badges" import { ProviderFormFields } from "./provider-form-fields" import type { ProviderDraftConfigItem } from "./types" import { formatConfigUpdatedAt, resolveErrorMessage, validateRequiredFields, } from "./utils" import { useProviderSelection } from "./use-provider-selection" import { useProviderConfigForm } from "./use-provider-config-form" interface ProviderConfigPanelProps { draftConfigMap: Record setDraftConfigMap: Dispatch< SetStateAction> > onCreateConfigIntent: (uploaderId: string) => void onDeleteConfigIntent: (uploaderId: string, configId: string) => void } export function ProviderConfigPanel({ draftConfigMap, setDraftConfigMap, onCreateConfigIntent, onDeleteConfigIntent, }: ProviderConfigPanelProps) { const { t } = useTranslation() const navigate = useNavigate() const { uploader, selectedUploaderId, selectedConfigId, storedSchema, selectedPersistedConfig, selectedConfig, isDraftSelected, isDefaultConfig, isLoading, canDelete, } = useProviderSelection({ draftConfigMap }) const { schema, values: formValues, fieldErrors, setFieldErrors, handleValueChange, } = useProviderConfigForm({ selectedUploaderId, selectedConfigId, isDraftSelected, selectedConfig, storedSchema, onDraftFieldChange: (uploaderId, name, value) => { setDraftConfigMap((prev) => { const draftConfig = prev[uploaderId] if (!draftConfig || draftConfig._id !== selectedConfigId) return prev return { ...prev, [uploaderId]: { ...draftConfig, _updatedAt: Date.now(), [name]: value, }, } }) }, }) const [isSaving, setIsSaving] = useState(false) useEffect(() => { if (!selectedUploaderId) return let isDisposed = false providerStoreActions .ensureSchema(selectedUploaderId) .catch((error) => { if (isDisposed) return toast.error(resolveErrorMessage(error, t("FAILED"))) }) return () => { isDisposed = true } }, [selectedUploaderId, t]) const navigateToSelection = ( uploaderId: string | undefined, configId: string | undefined ) => { navigate({ to: "/main/providers", search: (prev) => ({ ...prev, uploader: uploaderId, configId, }), }) } const handleSetDefaultConfig = async () => { if (!selectedUploaderId || !selectedPersistedConfig) return try { const selectedId = await providerStoreActions.setDefaultConfig( selectedUploaderId, selectedPersistedConfig._id ) navigateToSelection(selectedUploaderId, selectedId ?? undefined) toast.success(t("SUCCESS")) } catch (error) { toast.error(resolveErrorMessage(error, t("FAILED"))) } } const handleSave = async () => { if (!selectedUploaderId || !selectedConfig) return const missingRequiredFields = validateRequiredFields(schema, formValues) if (missingRequiredFields.length > 0) { const nextFieldErrors: Record = {} missingRequiredFields.forEach((fieldName) => { const field = schema.find((f) => f.name === fieldName) nextFieldErrors[fieldName] = t("FIELD_IS_REQUIRED", { field: field?.alias || field?.name || fieldName, }) }) setFieldErrors(nextFieldErrors) toast.error(t("FAILED")) return } setFieldErrors({}) setIsSaving(true) try { const filteredValues = filterValuesBySchema(schema, formValues) if (isDraftSelected) { const createdConfigId = await providerStoreActions.createConfig( selectedUploaderId, selectedConfig._configName ) if (createdConfigId) { await providerStoreActions.saveConfig( selectedUploaderId, createdConfigId, filteredValues ) } setDraftConfigMap((prev) => ({ ...prev, [selectedUploaderId]: undefined, })) navigateToSelection(selectedUploaderId, createdConfigId ?? undefined) } else { const selectedId = await providerStoreActions.saveConfig( selectedUploaderId, selectedConfig._id, filteredValues ) navigateToSelection(selectedUploaderId, selectedId ?? undefined) } toast.success(t("SUCCESS")) } catch (error) { toast.error(resolveErrorMessage(error, t("FAILED"))) } finally { setIsSaving(false) } } return (
{uploader?.name ?? t("ALBUM_PROVIDERS")} {uploader?.isDefaultUploader && ( )} {selectedConfig && ( <> / {selectedConfig._configName} {isDefaultConfig && ( )} {isDraftSelected && ( {t("PROVIDER_DRAFT_CONFIG")} )} )} } trailingClassName="gap-2" trailing={ <> } />
{!uploader && (
{t("PROVIDER_NO_UPLOADER_SELECTED")}
)} {uploader && isLoading && (
)} {uploader && !isLoading && !selectedConfig && (
{t("PROVIDER_NO_CONFIG_YET")}

{t("PROVIDER_NO_CONFIG_DESCRIPTION", { uploaderName: uploader.name, })}

)} {uploader && !isLoading && selectedConfig && (

{t("PROVIDER_CONFIGURATION")}

{t("PROVIDER_UPDATED_AT_LABEL")}:{" "} {formatConfigUpdatedAt(selectedConfig._updatedAt)}
{t("DELETE")}

{t("PROVIDER_DELETE_CONFIG_HINT")}

)}
) }