Files
Investidor-V2/resources/js/lib/partnership-room-delivery.ts
T

200 lines
7.3 KiB
TypeScript

import { useForm } from '@inertiajs/react';
import { useRef, useState } from 'react';
import { createOperationKey } from './operation-key';
type Data = {
body: string;
file: File | null;
shared: boolean;
client_key: string;
};
function object(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
/** Keeps the original command, including the immutable File object, until delivery is confirmed. */
export function useRoomDelivery({
roomId,
actorId,
kind,
mutable,
canWrite,
}: {
roomId: number;
actorId: number;
kind: 'message' | 'upload';
mutable: boolean;
canWrite: boolean;
}) {
const form = useForm<Data>({
body: '',
file: null,
shared: false,
client_key: createOperationKey(),
});
const live = useRef(form.data);
live.current = form.data;
const generation = useRef({ body: 0, file: 0, shared: 0 });
const identity = useRef(JSON.stringify([roomId, actorId, kind]));
const observed = useRef(identity.current);
observed.current = JSON.stringify([roomId, actorId, kind]);
const pending = useRef<{
data: Data;
generation: typeof generation.current;
ambiguous: boolean;
} | null>(null);
const [problem, setProblem] = useState<string | null>(null);
const recovering = !!pending.current?.ambiguous;
const invalid = observed.current !== identity.current;
const dirty =
!!pending.current ||
(kind === 'message' ? !!form.data.body : !!form.data.file);
const blocked =
form.processing || invalid || !canWrite || (!mutable && !recovering);
function change<K extends 'body' | 'file' | 'shared'>(
key: K,
value: Data[K],
) {
generation.current[key]++;
live.current = { ...live.current, [key]: value };
form.setData(live.current);
}
function submit() {
if (
blocked ||
(kind === 'upload' &&
!pending.current?.data.file &&
!live.current.file)
)
return;
const command = pending.current ?? {
data: { ...live.current },
generation: { ...generation.current },
ambiguous: false,
};
pending.current = command;
setProblem(null);
form.transform(() =>
kind === 'message'
? {
body: command.data.body,
client_key: command.data.client_key,
}
: {
file: command.data.file,
shared: command.data.shared,
client_key: command.data.client_key,
},
);
const unknown = () => {
if (pending.current) pending.current.ambiguous = true;
setProblem(
'O envio não foi confirmado. A tentativa original e a sua preparação foram conservadas.',
);
return false;
};
form.post(
`/parcerias/salas/${roomId}/${kind === 'message' ? 'mensagens' : 'documentos'}`,
{
preserveScroll: true,
forceFormData: kind === 'upload',
onSuccess: (page) => {
if (observed.current !== identity.current) return;
const props = page.props;
const room = props.room;
const flash = props.flash;
const confirmed =
object(room) &&
room.id === roomId &&
(kind === 'message'
? Array.isArray(props.messages) &&
props.messages.some(
(message) =>
object(message) &&
message.author_id === actorId &&
message.client_key ===
command.data.client_key &&
message.body === command.data.body.trim(),
)
: object(flash) &&
typeof flash.uploaded_document_id === 'string' &&
Array.isArray(props.documents) &&
props.documents.some(
(document) =>
object(document) &&
document.document_id ===
flash.uploaded_document_id &&
document.owner_id === actorId &&
document.file_name ===
command.data.file?.name,
));
if (!confirmed) {
unknown();
return;
}
const next: Data = {
body: '',
file: null,
shared: false,
client_key: createOperationKey(),
};
if (
kind === 'message' &&
generation.current.body !== command.generation.body
)
next.body = live.current.body;
if (kind === 'upload') {
if (
generation.current.file !== command.generation.file
) {
next.file = live.current.file;
next.shared = live.current.shared;
} else if (
generation.current.shared !==
command.generation.shared
)
next.shared = live.current.shared;
}
pending.current = null;
live.current = next;
form.setDefaults({
body: '',
file: null,
shared: false,
client_key: next.client_key,
});
form.setData(next);
form.clearErrors();
setProblem(null);
},
onError: () => {
if (!pending.current?.ambiguous) pending.current = null;
},
onHttpException: (response) => {
if (
response.status >= 500 ||
response.status === 409 ||
pending.current?.ambiguous
)
return unknown();
pending.current = null;
setProblem(
'Não foi possível confirmar o envio com o acesso actual. A preparação foi conservada.',
);
return false;
},
onNetworkError: unknown,
onCancel: unknown,
},
);
}
return {
form,
data: form.data,
change,
submit,
problem,
recovering,
dirty,
blocked,
};
}