HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux WebLive 5.15.0-79-generic #86-Ubuntu SMP Mon Jul 10 16:07:21 UTC 2023 x86_64
User: ubuntu (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/html/wpmuhibbah_err/wp-content/plugins/give/src/Admin/components/PrivateNotes/index.tsx
import {__} from '@wordpress/i18n';
import {addQueryArgs} from '@wordpress/url';
import useSWR from 'swr';
import React, {useState} from 'react';
import apiFetch from '@wordpress/api-fetch';
import {useDispatch} from '@wordpress/data';
import {ConfirmationDialogIcon, DeleteIcon, DotsMenuIcon, EditIcon, NotesIcon} from './Icons';
import Spinner from '../Spinner';
import ModalDialog from '@givewp/components/AdminUI/ModalDialog';
import style from './style.module.scss';
import cx from 'classnames';
import {formatTimestamp} from '@givewp/src/Admin/utils';
import Header from '@givewp/src/Admin/components/Header';

/**
 * @since 4.5.0
 */
type DonorNote = {
    id: number;
    donorId: number;
    content: string;
    createdAt: {
        date: string;
    };
}

/**
 * @since 4.5.0
 */
type NoteState = {
    isAddingNote: boolean;
    isSavingNote: boolean;
    note: string;
    perPage: number;
}

/**
 * @since 4.6.0
 */
export function DonorNotes({donorId}: {donorId: number}) {
    return <PrivateNotes endpoint={`/givewp/v3/donors/${donorId}/notes`} />
}

/**
 * @since 4.6.0
 */
export function DonationNotes({donationId}: {donationId: number}) {
    return <PrivateNotes endpoint={`/givewp/v3/donations/${donationId}/notes`} />
}

/**
 * @since 4.4.0
 */
function PrivateNotes({endpoint}: {endpoint: string}) {
    const [state, setNoteState] = useState<NoteState>({
        isAddingNote: false,
        isSavingNote: false,
        note: '',
        perPage: 5,
    });

    const dispatch = useDispatch('givewp/admin-details-page-notifications');

    const {
        data,
        isLoading,
        isValidating,
        mutate,
    } = useSWR<{data: DonorNote[]; totalPages: number; totalItems: number}>(endpoint, async (url) => {
        const response = await apiFetch({
            path: addQueryArgs(url, {page: 1, per_page: state.perPage}),
            parse: false,
        }) as Response;
        const data = await response.json();
        return {
            data,
            totalPages: Number(response.headers.get('X-WP-TotalPages')),
            totalItems: Number(response.headers.get('X-WP-Total')),
        };
    }, {revalidateOnFocus: false});

    const saveNote = () => {
        setState({isSavingNote: true});
        apiFetch({path: endpoint, method: 'POST', data: {content: state.note}})
            .then((response) => {
                mutate(response).then(() => {
                    setState({isAddingNote: false})
                    dispatch.addSnackbarNotice({
                        id: 'add-note',
                        content: __('You added a private note', 'give'),
                    });
                });
            });
    };

    const deleteNote = (id: number) => {
        apiFetch({path: `${endpoint}/${id}`, method: 'DELETE', data: {id}})
            .then(async (response) => {
                await mutate(response);
                dispatch.addSnackbarNotice({
                    id: 'delete-note',
                    content: __('Private note deleted successfully', 'give'),
                });
            });
    };

    const editNote = (id: number, content: string) => {
        apiFetch({path: `${endpoint}/${id}`, method: 'PATCH', data: {content}})
            .then(async (response) => {
                await mutate(response);
                dispatch.addSnackbarNotice({
                    id: 'edit-note',
                    content: __('Private note edited', 'give'),
                });
            });
    };

    const setState = (props) => {
        setNoteState((prevState) => {
            return {
                ...prevState,
                ...props,
            };
        });
    };

    if (isLoading || isValidating) {
        return (
            <div style={{margin: '0 auto'}}>
                <Spinner />
            </div>
        );
    }

    return (
        <>
            <Header
                title={__('Private Note', 'give')}
                subtitle={__('This note will be seen by only admins', 'give')}
                actionOnClick={() => setState({isAddingNote: true})}
                actionText={__('Add note', 'give')}
            />
            <div className={style.notesContainer}>
                {state.isAddingNote && (
                    <div className={style.addNoteContainer}>
                    <textarea
                        className={style.textarea}
                        onChange={(e) => setState({note: e.target.value})}
                    ></textarea>

                        <div className={style.textAreaButtons}>
                            <button
                                className={cx(style.button, style.cancelBtn)}
                                onClick={() => setState({isAddingNote: false})}
                            >
                                {__('Cancel', 'give')}
                            </button>
                            <button
                                className={cx(style.button, style.saveBtn)}
                                onClick={(e) => {
                                    e.preventDefault();
                                    saveNote();
                                }}
                            >
                                {__('Save', 'give')}
                            </button>
                        </div>
                    </div>
                )}
                {data?.data?.length ? (
                    <>
                        {data.data.map((note) => {
                            return (
                                <Note
                                    key={note.id}
                                    note={note}
                                    onDelete={(id: number) => deleteNote(id)}
                                    onEdit={(id: number, content: string) => editNote(id, content)}
                                />
                            );
                        })}
                    </>
                ) : (
                    <>
                        {!state.isAddingNote && (
                            <div style={{margin: '0 auto', textAlign: 'center'}}>
                                <NotesIcon />
                                <p className={style.noNotesText}>{__('No notes yet', 'give')}</p>
                            </div>
                        )}
                    </>
                )}

                <div className={style.showMoreContainer}>
                    {data?.data?.length > 0 && data.totalItems > state.perPage && (
                        <button
                            className={style.showMoreButton}
                            onClick={async (e) => {
                                e.preventDefault();
                                setNoteState((prevState) => {
                                    return {
                                        ...prevState,
                                        perPage: prevState.perPage += 5,
                                    };
                                });

                                await mutate(endpoint);
                            }}>
                            {__('Show more', 'give')}
                        </button>
                    )}
                </div>
            </div>
        </>
    );
}


/**
 * @since 4.4.0
 */
const Note = ({note, onDelete, onEdit}) => {
    const [showContextMenu, setShowContextMenu] = useState(false);
    const [currentlyEditing, setCurrentlyEditing] = useState(null);
    const [content, setContent] = useState(note.content);
    const [showDeleteDialog, setShowDeleteDialog] = useState(false);

    return (
        <>
            <div
                onMouseLeave={() => {
                    setShowContextMenu(false);
                }}
            >
                {currentlyEditing ? (
                    <>
                        <div className={style.addNoteContainer}>
                            <textarea
                                className={style.textarea}
                                onChange={(e) => setContent(e.target.value)}
                                value={content}
                            ></textarea>

                            <div className={style.textAreaButtons}>
                                <button
                                    className={cx(style.button, style.cancelBtn)}
                                    onClick={() => {
                                        setCurrentlyEditing(null);
                                        setShowContextMenu(false);
                                    }}
                                >
                                    {__('Cancel', 'give')}
                                </button>
                                <button
                                    className={cx(style.button, style.saveBtn)}
                                    onClick={(e) => {
                                        e.preventDefault();
                                        setShowContextMenu(false);
                                        onEdit(note.id, content);
                                    }}
                                >
                                    {__('Save', 'give')}
                                </button>
                            </div>
                        </div>
                    </>
                ) : (
                    <>
                        <div className={style.noteContainer}>
                            <div className={style.note}>
                                <div className={style.title}>
                                    {note.content}
                                </div>

                                <div
                                    className={style.dotsMenu}
                                    onClick={() => setShowContextMenu(true)}
                                >
                                    <DotsMenuIcon />
                                    {showContextMenu && (
                                        <div className={style.menu}>
                                            <a
                                                href="#"
                                                className={style.menuItem}
                                                onClick={(e) => {
                                                    e.preventDefault();
                                                    setShowContextMenu(false);
                                                    setCurrentlyEditing(note.id);
                                                }}
                                            >
                                                <EditIcon /> {__('Edit', 'give')}
                                            </a>
                                            <a
                                                href="#"
                                                className={cx(style.menuItem, style.delete)}
                                                onClick={(e) => {
                                                    e.preventDefault();
                                                    setShowContextMenu(false);
                                                    setShowDeleteDialog(true);
                                                }}
                                            >
                                                <DeleteIcon /> {__('Delete', 'give')}
                                            </a>
                                        </div>
                                    )}
                                </div>
                            </div>
                            <div className={style.date}>
                                {formatTimestamp(note.createdAt.date)}
                            </div>
                        </div>
                    </>
                )}
                <ConfirmationDialog
                    title={__('Delete Note', 'give')}
                    isOpen={showDeleteDialog}
                    handleClose={() => setShowDeleteDialog(false)}
                    handleConfirm={() => {
                        onDelete(note.id);
                    }}
                />
            </div>
        </>
    );
};


/**
 * @since 4.5.0
 */
function ConfirmationDialog({
    isOpen,
    title,
    handleClose,
    handleConfirm
}: {
    isOpen: boolean;
    handleClose: () => void;
    handleConfirm: () => void;
    title: string;
}) {
    return (
        <ModalDialog
            icon={<ConfirmationDialogIcon />}
            isOpen={isOpen}
            showHeader={true}
            handleClose={handleClose}
            title={title}
        >
            <>
                <div className={style.dialogContent}>
                    {__('Are you sure you want to delete this note?', 'give')}
                </div>
                <div className={style.dialogButtons}>
                    <button
                        className={style.cancelButton}
                        onClick={handleClose}
                    >
                        {__('Cancel', 'give')}
                    </button>
                    <button
                        className={style.confirmButton}
                        onClick={handleConfirm}
                    >
                        {__('Delete note', 'give')}
                    </button>
                </div>
            </>
        </ModalDialog>
    );
}