import React, { ReactNode, useState } from 'react' import { Annotation, ANNOTATION_TYPE_LABELS, AnnotationGranularity, AnnotationPayload, AnnotationType, canEditAnnotation, NOTE_MAX_LENGTH } from './annotations' import { MutationStatus } from '@tanstack/react-query' import { ApiError } from '../api' import { ErrorPanel } from '../components/error-panel' import { ModalLayout, ModalFooter, SaveButton } from '../components/modal-layout' import { LabeledTextarea, TypeSelector, getOptionDisabledMessage, isOverMaxLength, OptionDisabledMessageType } from '../components/form-elements' import { Button } from '../components/button' import { UpgradePill } from '../components/pill' import { Role, UserContextValue } from '../user-context' import { formatDay, formatTime, is12HourClock, parseUTCDate } from '../util/date' const formatAnnotationDatetime = ( datetime: string, granularity: AnnotationGranularity ): string => { const date = parseUTCDate(datetime) if (granularity === AnnotationGranularity.minute) { const time = formatTime(date, { use12HourClock: is12HourClock(), includeMinutes: true }) return `${formatDay(date)} at ${time}` } return formatDay(date) } interface ApiRequestProps { status: MutationStatus error?: unknown reset: () => void } interface AnnotationModalProps { user: UserContextValue siteAnnotationsAvailable: boolean onClose: () => void notePlaceholder: string } export const CreateAnnotationModal = ({ onClose, onSave, user, siteAnnotationsAvailable, notePlaceholder, initialDatetime, initialGranularity, initialType, error, reset, status }: AnnotationModalProps & ApiRequestProps & { initialDatetime: AnnotationPayload['datetime'] initialGranularity: AnnotationPayload['granularity'] initialType: AnnotationPayload['type'] } & { onSave: (input: AnnotationPayload) => void }) => { const [note, setNote] = useState('') const [type, setType] = useState(initialType) const trimmedNote = note.trim() const granularity = initialGranularity const datetime = initialDatetime const siteOptionDisabledMessage = getAnnotationTypeDisabledMessage({ siteAnnotationsAvailable, user }) const disabledMessage = type === AnnotationType.site ? siteOptionDisabledMessage : null return ( onSave({ note: trimmedNote, type, datetime, granularity }) } /> {error !== null && ( )} ) } const AnnotationTypeSelector = ({ value, onChange, siteOptionDisabledMessage }: { value: AnnotationType onChange: (value: AnnotationType) => void siteOptionDisabledMessage: OptionDisabledMessageType | null }) => ( idPrefix="annotation-type" value={value} onChange={onChange} options={[ { type: AnnotationType.personal, name: ANNOTATION_TYPE_LABELS[AnnotationType.personal], description: 'Visible only to you' }, { type: AnnotationType.site, name: ANNOTATION_TYPE_LABELS[AnnotationType.site], description: 'Visible to others on the site', disabled: siteOptionDisabledMessage !== null, pill: siteOptionDisabledMessage === 'upgrade-subscription-yourself' || siteOptionDisabledMessage === 'upgrade-subscription-reach-out' ? ( ) : null, tooltipContent: siteOptionDisabledMessage !== null ? ( ) : null } ]} /> ) const AnnotationTypeDisabledMessage = ({ messageType }: { messageType: OptionDisabledMessageType }): Exclude => { switch (messageType) { case 'no-permissions': return "You don't have enough permissions to change note to this type" case 'upgrade-subscription-yourself': return 'Upgrade your plan to make notes visible to others.' case 'upgrade-subscription-reach-out': return 'Ask a team owner to upgrade your plan to make notes visible to others.' } } const getAnnotationTypeDisabledMessage = ({ siteAnnotationsAvailable, user }: { siteAnnotationsAvailable: boolean user: UserContextValue }): OptionDisabledMessageType | null => getOptionDisabledMessage({ optionAvailable: siteAnnotationsAvailable, userHasOptionPermissions: canEditAnnotation({ type: AnnotationType.site, user }), userCanUpgradeSubscription: user.role === Role.owner }) export const DeleteAnnotationModal = ({ annotation, onClose, onSave, status, error, reset }: { onClose: () => void onSave: (input: Pick) => void annotation: Annotation } & ApiRequestProps) => { const deleteDisabled = status === 'pending' return ( Delete {ANNOTATION_TYPE_LABELS[annotation.type].toLowerCase()} {` "${annotation.note}"?`} } onClose={onClose} > {error !== null && ( )} ) } export const UpdateAnnotationModal = ({ onClose, onSave, onDelete, annotation, siteAnnotationsAvailable, user, notePlaceholder, status, error, reset }: AnnotationModalProps & ApiRequestProps & { onSave: (input: Pick) => void onDelete: (annotation: Annotation) => void annotation: Annotation }) => { const [note, setNote] = useState(annotation.note) const [type, setType] = useState(annotation.type) const trimmedNote = note.trim() const siteOptionDisabledMessage = getAnnotationTypeDisabledMessage({ siteAnnotationsAvailable, user }) const disabledMessage = type === AnnotationType.site ? siteOptionDisabledMessage : null return ( onSave({ id: annotation.id, note: trimmedNote, type })} /> {error !== null && ( )} ) } const isSaveButtonDisabled = ({ requestStatus, disabledMessage, trimmedNote }: { requestStatus: MutationStatus trimmedNote: string disabledMessage: string | null }) => requestStatus === 'pending' || isOverMaxLength(trimmedNote, NOTE_MAX_LENGTH) || disabledMessage !== null || !trimmedNote.length