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