import * as yup from "yup" import {TiDelete} from "react-icons/ti" import {AxiosError} from "axios" import {ReactElement, useContext, useMemo, useState} from "react" import {MdCheckCircle, MdEditCalendar} from "react-icons/md" import {RiStickyNoteFill} from "react-icons/ri" import {FieldArray, FormikProvider, useFormik} from "formik" import {FaPen} from "react-icons/fa" import {useTranslation} from "react-i18next" import deepEqual from "deep-equal" import format from "date-fns/format" import update from "immutability-helper" import {QueryKey, useMutation} from "@tanstack/react-query" import { Grid, IconButton, InputAdornment, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, TextField, Tooltip, Typography, } from "@mui/material" import {parseFastAPIError} from "~/utils" import {FaviconImage, SimpleOverlayInformation} from "~/components" import {Alias, AliasNote, DecryptedAlias} from "~/server-types" import {updateAlias} from "~/apis" import {useErrorSuccessSnacks} from "~/hooks" import {queryClient} from "~/constants/react-query" import AddWebsiteField from "~/route-widgets/AliasDetailRoute/AddWebsiteField" import AuthContext from "~/AuthContext/AuthContext" import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation" import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes" export interface AliasNotesFormProps { id: string notes: AliasNote queryKey: QueryKey } interface Form { personalNotes: string websites: AliasNote["data"]["websites"] detail?: string } export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProps): ReactElement { const {t} = useTranslation() const {showError, showSuccess} = useErrorSuccessSnacks() const {_encryptUsingMasterPassword, _decryptUsingMasterPassword} = useContext(AuthContext) const schema = yup.object().shape({ personalNotes: yup .string() .label(t("routes.AliasDetailRoute.sections.notes.form.personalNotes.label")), websites: yup.array().of( yup .object() .shape({ url: yup.string().url(), }) .label(t("routes.AliasDetailRoute.sections.notes.form.websites.label")), ), }) const {mutateAsync} = useMutation< Alias, AxiosError, AliasNote, {previousAlias?: DecryptedAlias} >( notes => { const encryptedNotes = _encryptUsingMasterPassword(JSON.stringify(notes)) return updateAlias(id, { encryptedNotes, }) }, { onMutate: async notes => { await queryClient.cancelQueries(queryKey) const previousAlias = queryClient.getQueryData(queryKey) if (previousAlias) { ;(previousAlias as any as DecryptedAlias).notes = decryptAliasNotes( (previousAlias as any as Alias).encryptedNotes, _decryptUsingMasterPassword, ) } queryClient.setQueryData(queryKey, old => update(old, {notes: {$set: notes}}), ) return { previousAlias, } }, onSuccess: async newAlias => { ;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes( newAlias.encryptedNotes, _decryptUsingMasterPassword, ) showSuccess(t("relations.alias.mutations.success.notesUpdated")) await queryClient.cancelQueries(queryKey) queryClient.setQueryData( queryKey, newAlias as any as DecryptedAlias, ) }, onError: (error, _, context) => { showError(error) setIsInEditMode(true) if (context?.previousAlias) { queryClient.setQueryData(queryKey, context.previousAlias) } }, }, ) const initialValues = useMemo( () => ({ personalNotes: notes.data.personalNotes, websites: notes.data.websites, }), [notes.data.personalNotes, notes.data.websites], ) const formik = useFormik
({ validationSchema: schema, initialValues, onSubmit: async (values, {setErrors}) => { try { const newNotes = update(notes, { data: { personalNotes: { $set: values.personalNotes, }, websites: { $set: values.websites, }, }, }) await mutateAsync(newNotes) } catch (error) { setErrors(parseFastAPIError(error as AxiosError)) } }, }) const [isInEditMode, setIsInEditMode] = useState(false) return ( <> {t("routes.AliasDetailRoute.sections.notes.title")} { setIsInEditMode(!isInEditMode) if ( isInEditMode && !deepEqual(initialValues, formik.values, { strict: true, }) ) { await formik.submitForm() } }} > {isInEditMode ? : } {notes.data.createdAt && ( } label={t( "routes.AliasDetailRoute.sections.notes.form.createdAt.label", )} > {notes.data.createdAt && ( {format(notes.data.createdAt, "Pp")} )} )} {isInEditMode ? ( ), }} /> ) : ( notes.data.personalNotes )} {isInEditMode ? ( { await formik.setFieldValue("websites", [ ...formik.values.websites, { url: website, }, ]) }} isLoading={formik.isSubmitting} /> ( {formik.values.websites.map( (website, index) => ( {website.url} { arrayHelpers.remove( index, ) }} > ), )} )} /> ) : notes.data.websites.length ? ( {notes.data.websites.map(website => ( {website.url} ))} ) : null} ) }