'use client'; import { ActionIcon, Button, Card, Combobox, Divider, Group, Input, ScrollArea, Stack, Text, Textarea, ThemeIcon, Tooltip, useCombobox, VisuallyHidden, } from '@mantine/core'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; import { useRef } from 'react'; import useCreateConnection from '../../lib/mutations/useCreateConnection'; import {} from 'react-icons/io'; import { LuChevronsUpDown, LuArrowUpDown } from 'react-icons/lu'; import { CONNECTION_TYPES } from '../../const/connectionTypes'; import UrlSearchInput from './UrlSearchInput'; import SourceCardPreview from './SourceCardPreview'; import { BsCheck, BsExclamation } from 'react-icons/bs'; import { BiSolidChevronDown } from 'react-icons/bi'; interface Props { onClose: () => void; /** When provided the source is fixed (connecting from a card). When omitted both URLs are searchable. */ sourceUrl?: string; /** When provided the target is prefilled. */ targetUrl?: string; } export default function AddConnectionForm(props: Props) { const hasFixedSource = !!props.sourceUrl; const createConnection = useCreateConnection(); // Track the raw input values so we can auto-confirm valid URLs on submit const rawSourceInput = useRef(props.sourceUrl ?? ''); const rawTargetInput = useRef(props.targetUrl ?? ''); const typeCombobox = useCombobox({ onDropdownClose: () => typeCombobox.resetSelectedOption(), }); const form = useForm({ initialValues: { sourceUrl: props.sourceUrl ?? '', targetUrl: props.targetUrl ?? '', connectionType: 'RELATED', note: '', }, validateInputOnChange: false, validateInputOnBlur: true, validate: { sourceUrl: (value) => { if (!value || value.trim() === '') { return 'Source URL is required'; } try { new URL(value); return null; } catch { return 'Please enter a valid URL'; } }, targetUrl: (value) => { if (!value || value.trim() === '') { return 'Please enter a URL'; } try { new URL(value); return null; } catch { return 'Please enter a valid URL'; } }, }, }); const MAX_NOTE_LENGTH = 500; const handleSwapUrls = () => { const currentSource = form.values.sourceUrl; const currentTarget = form.values.targetUrl; form.setFieldValue('sourceUrl', currentTarget); form.setFieldValue('targetUrl', currentSource); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // Auto-confirm valid URLs that were typed/pasted but not explicitly selected if (!form.values.sourceUrl && rawSourceInput.current) { try { new URL(rawSourceInput.current); form.setFieldValue('sourceUrl', rawSourceInput.current); } catch { // not a valid URL, let validation handle it } } if (!form.values.targetUrl && rawTargetInput.current) { try { new URL(rawTargetInput.current); form.setFieldValue('targetUrl', rawTargetInput.current); } catch { // not a valid URL, let validation handle it } } const validation = form.validate(); if (validation.hasErrors) { return; } const values = form.getValues(); if (values.sourceUrl === values.targetUrl) { notifications.show({ id: 'same-url-error', title: 'A link cannot be connected to itself', message: 'Please choose a different link to connect', }); return; } createConnection.mutate( { sourceUrl: values.sourceUrl, targetUrl: values.targetUrl, connectionType: values.connectionType ? (values.connectionType as any) : undefined, note: values.note || undefined, }, { onSuccess: () => { props.onClose(); notifications.show({ color: 'green', title: 'Success!', message: 'Connection created', position: 'top-center', loading: false, autoClose: 2000, icon: , }); }, onError: () => { notifications.show({ message: 'Could not create connection.', color: 'red', autoClose: 5000, withCloseButton: true, position: 'top-center', icon: , }); }, onSettled: () => { form.reset(); }, }, ); }; // ---- Source slot ---- const sourceSlot = hasFixedSource ? ( From ) : ( form.setFieldValue('sourceUrl', url)} onInputChange={(raw) => { rawSourceInput.current = raw; }} /> {form.errors.sourceUrl && ( {form.errors.sourceUrl} )} ); // ---- Target slot ---- const targetSlot = ( form.setFieldValue('targetUrl', url)} onInputChange={(raw) => { rawTargetInput.current = raw; }} /> {form.errors.targetUrl && ( {form.errors.targetUrl} )} ); // ---- Connection type selector ---- const connectionTypeSelector = ( { form.setFieldValue('connectionType', value); typeCombobox.closeDropdown(); }} > {CONNECTION_TYPES.map((type) => { const Icon = type.icon; const isSelected = form.values.connectionType === type.value; return ( {Icon && } {type.label} {type.description} ); })} ); return (
{/* Source → Type selector → Target (single layout for both modes) */} {sourceSlot} {connectionTypeSelector} {targetSlot} Note {form.getValues().note.length} / {MAX_NOTE_LENGTH}