refactor: reduce complexity
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
import { api } from "../api/apiClient";
|
||||
import type { LetterResponseData } from "../api/response";
|
||||
import {
|
||||
type CanvasStyle,
|
||||
type CanvasTools,
|
||||
@@ -26,7 +27,6 @@ import DateDisplay from "../components/ui/DateDisplay";
|
||||
import { LogModal } from "../components/ui/LogModal";
|
||||
import { Modal } from "../components/ui/Modal";
|
||||
import { Navbar } from "../components/ui/Navbar";
|
||||
|
||||
import { endpoints } from "../config/endpoints";
|
||||
import { PATHS } from "../config/routes";
|
||||
import { useKeyStore } from "../store/useKeyStore";
|
||||
@@ -116,26 +116,11 @@ export default function Editor() {
|
||||
justSavedRef.current = false;
|
||||
return;
|
||||
}
|
||||
const loadExistingLetter = async () => {
|
||||
setIsInitialLoading(true);
|
||||
const decryptAndLoadLetter = async (
|
||||
letterData: LetterResponseData,
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const cryptoUtils = new CryptoUtils();
|
||||
|
||||
try {
|
||||
const res = await api.get(`${endpoints.LETTERS}${public_id}/`);
|
||||
const letterData = res.data;
|
||||
|
||||
setLastSaved(formatRelativeDate(new Date(letterData.updated_at)));
|
||||
setLetterStatus(letterData.status);
|
||||
|
||||
if (letterData.status === "SEALED") {
|
||||
navigateRef.current(PATHS.read(public_id), { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!letterData.encrypted_dek) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = await cryptoUtils.decryptMetadata(
|
||||
{
|
||||
encrypted_content: letterData.encrypted_metadata,
|
||||
@@ -167,8 +152,7 @@ export default function Editor() {
|
||||
if (isPartialFailure) {
|
||||
setDecryptionStatus({
|
||||
status: "WARN",
|
||||
message:
|
||||
"Failed to decrypt some elements. Please check the render.",
|
||||
message: "Failed to decrypt some elements. Please check the render.",
|
||||
log: errors.toString(),
|
||||
});
|
||||
}
|
||||
@@ -176,11 +160,30 @@ export default function Editor() {
|
||||
if (canvasRef.current) {
|
||||
await canvasRef.current.loadData(canvasDataWithDecryptedImages);
|
||||
}
|
||||
} catch (_err) {
|
||||
};
|
||||
|
||||
const loadExistingLetter = async () => {
|
||||
setIsInitialLoading(true);
|
||||
try {
|
||||
const res = await api.get(`${endpoints.LETTERS}${public_id}/`);
|
||||
const letterData = res.data;
|
||||
|
||||
setLastSaved(formatRelativeDate(new Date(letterData.updated_at)));
|
||||
setLetterStatus(letterData.status);
|
||||
|
||||
if (letterData.status === "SEALED") {
|
||||
navigateRef.current(PATHS.read(public_id), { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (letterData.encrypted_dek && masterKey) {
|
||||
await decryptAndLoadLetter(letterData, masterKey);
|
||||
}
|
||||
} catch (err) {
|
||||
setDecryptionStatus({
|
||||
status: "ERROR",
|
||||
message: "Failed to decrypt letter. Please try again later.",
|
||||
log: _err instanceof Error ? _err.message : "Unknown error",
|
||||
log: err instanceof Error ? err.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsInitialLoading(false);
|
||||
@@ -242,76 +245,79 @@ export default function Editor() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async (
|
||||
status: "SEALED" | "DRAFT" | "VAULT",
|
||||
const getRequestData = async (
|
||||
targetId: string,
|
||||
status: string,
|
||||
vaultDate?: Date,
|
||||
): Promise<void> => {
|
||||
setSealBtnClicked(false);
|
||||
|
||||
let targetId = public_id || letterIdRef.current;
|
||||
if (!targetId) {
|
||||
targetId = crypto.randomUUID();
|
||||
}
|
||||
|
||||
if (saveOverlay === "SAVING" || !masterKey) return;
|
||||
|
||||
setSaveOverlay("SAVING");
|
||||
setShowSaveOverlay(true);
|
||||
|
||||
): Promise<FormData> => {
|
||||
const cryptoUtils = new CryptoUtils();
|
||||
await cryptoUtils.initialize();
|
||||
|
||||
try {
|
||||
const canvasData = (await canvasRef.current?.getData()) || {
|
||||
objects: [],
|
||||
};
|
||||
const canvasData = (await canvasRef.current?.getData()) || { objects: [] };
|
||||
const canvasImages = canvasRef.current?.getImages() || [];
|
||||
|
||||
const { encryptedImageFiles, encryptedCanvasData } =
|
||||
await encryptCanvasImages(
|
||||
canvasData,
|
||||
canvasImages,
|
||||
masterKey,
|
||||
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||
masterKey!,
|
||||
cryptoUtils,
|
||||
);
|
||||
|
||||
const encrypted_letter = await cryptoUtils.encryptLetter(
|
||||
JSON.stringify(encryptedCanvasData),
|
||||
masterKey,
|
||||
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||
masterKey!,
|
||||
);
|
||||
|
||||
const encrypted_metadata = await cryptoUtils.encryptMetadata(
|
||||
{ recipient, tags: [] },
|
||||
masterKey,
|
||||
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||
masterKey!,
|
||||
);
|
||||
|
||||
const formData = new FormData();
|
||||
if (status === "VAULT") {
|
||||
const finalDate = vaultDate || unlockDate;
|
||||
formData.append("type", "VAULT");
|
||||
if (finalDate) {
|
||||
formData.append("unlock_at", finalDate.toISOString());
|
||||
}
|
||||
if (finalDate) formData.append("unlock_at", finalDate.toISOString());
|
||||
formData.append("status", "SEALED");
|
||||
} else {
|
||||
formData.append("type", "KEPT");
|
||||
formData.append("status", status);
|
||||
}
|
||||
|
||||
formData.append("public_id", targetId);
|
||||
formData.append("encrypted_content", encrypted_letter.encrypted_content);
|
||||
formData.append("encrypted_dek", encrypted_letter.encrypted_dek);
|
||||
formData.append(
|
||||
"encrypted_metadata",
|
||||
encrypted_metadata.encrypted_content,
|
||||
);
|
||||
formData.append("encrypted_metadata", encrypted_metadata.encrypted_content);
|
||||
|
||||
encryptedImageFiles.forEach((blob, filename) => {
|
||||
formData.append("image_files", blob, filename);
|
||||
});
|
||||
|
||||
await api.put(`${endpoints.LETTERS}${targetId}/`, formData);
|
||||
justSavedRef.current = true;
|
||||
return formData;
|
||||
};
|
||||
|
||||
const handleSave = async (
|
||||
status: "SEALED" | "DRAFT" | "VAULT",
|
||||
vaultDate?: Date,
|
||||
): Promise<void> => {
|
||||
setSealBtnClicked(false);
|
||||
// use the letter's id if an existing letter or create a new id
|
||||
const targetId = public_id || letterIdRef.current || crypto.randomUUID();
|
||||
|
||||
if (saveOverlay === "SAVING" || !masterKey) return;
|
||||
|
||||
setSaveOverlay("SAVING");
|
||||
setShowSaveOverlay(true);
|
||||
|
||||
try {
|
||||
const formData = await getRequestData(targetId, status, vaultDate);
|
||||
await api.put(`${endpoints.LETTERS}${targetId}/`, formData);
|
||||
|
||||
justSavedRef.current = true;
|
||||
if (!public_id) {
|
||||
letterIdRef.current = targetId;
|
||||
navigate(PATHS.write(targetId), { replace: true });
|
||||
@@ -326,7 +332,7 @@ export default function Editor() {
|
||||
}
|
||||
setSaveOverlay("SAVED");
|
||||
setShowSaveOverlay(true);
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
setSaveOverlay("ERROR");
|
||||
setShowSaveOverlay(true);
|
||||
}
|
||||
@@ -337,8 +343,7 @@ export default function Editor() {
|
||||
<Navbar
|
||||
child={
|
||||
<div
|
||||
className={`flex items-center gap-2 ${
|
||||
isSaveDatePulsing ? "animate-pulse" : ""
|
||||
className={`flex items-center gap-2 ${isSaveDatePulsing ? "animate-pulse" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-xxs text-neutral-content/30 flex-col justify-end leading-none text-right">
|
||||
@@ -391,8 +396,7 @@ export default function Editor() {
|
||||
{saveOverlay === "SAVING" && (
|
||||
<div
|
||||
role="alert"
|
||||
className={`alert text-center alert-neutral shadow-lg transition-all ease-in-out duration-2000 ${
|
||||
showSaveOverlay
|
||||
className={`alert text-center alert-neutral shadow-lg transition-all ease-in-out duration-2000 ${showSaveOverlay
|
||||
? "opacity-100 scale-100 translate-y-0"
|
||||
: "opacity-0 scale-95 translate-y-1"
|
||||
}`}
|
||||
@@ -410,8 +414,7 @@ export default function Editor() {
|
||||
<div
|
||||
role="alert"
|
||||
data-testid="save-success-toast"
|
||||
className={`alert alert-success shadow-lg transition-all ease-in-out duration-2000 ${
|
||||
showSaveOverlay
|
||||
className={`alert alert-success shadow-lg transition-all ease-in-out duration-2000 ${showSaveOverlay
|
||||
? "opacity-100 scale-100 translate-y-0"
|
||||
: "opacity-0 scale-95 translate-y-1"
|
||||
}`}
|
||||
@@ -424,8 +427,7 @@ export default function Editor() {
|
||||
{saveOverlay === "ERROR" && (
|
||||
<div
|
||||
role="alert"
|
||||
className={`alert alert-error shadow-lg transition-all duration-300 ${
|
||||
showSaveOverlay
|
||||
className={`alert alert-error shadow-lg transition-all duration-300 ${showSaveOverlay
|
||||
? "opacity-100 scale-100 translate-y-0"
|
||||
: "opacity-0 scale-95 translate-y-1"
|
||||
}`}
|
||||
|
||||
@@ -73,7 +73,8 @@ export default function Reader() {
|
||||
const key = await cryptoUtils.extractSharingKey(encryptedDek, masterKey);
|
||||
try {
|
||||
await api.patch(`${endpoints.LETTERS}${public_id}/`, { type: "SENT" });
|
||||
} catch (_err) {
|
||||
} catch {
|
||||
// shouldn't obstruct share if api operation fails (since it's client side share)
|
||||
} finally {
|
||||
setShareLink(`${window.location.origin}${PATHS.read(public_id)}#${key}`);
|
||||
}
|
||||
@@ -86,7 +87,10 @@ export default function Reader() {
|
||||
await api.patch(`${endpoints.LETTERS}${public_id}/`, {
|
||||
status: "BURNED",
|
||||
});
|
||||
} catch (_err) {
|
||||
} catch {
|
||||
// should not obstruct burn if api operation fails
|
||||
// WHY?: it disconnects the UX. if you want to burn the letter, you should be able to burn the letter
|
||||
// TODO: maybe say something like: "the wind is strong today, let's try again"? or maybe something less stupid :3
|
||||
} finally {
|
||||
setIsBurning(false);
|
||||
setShowBurnModal(false);
|
||||
@@ -268,8 +272,7 @@ export default function Reader() {
|
||||
<section className="min-h-fit w-full bg-base-100 px-4 py-8 md:py-16 font-serif relative overflow-hidden">
|
||||
<div className="fixed inset-0 bg-vig pointer-events-none z-0" />
|
||||
<div
|
||||
className={`transition-all delay-300 duration-1000 relative ${
|
||||
revealState === "REVEALED"
|
||||
className={`transition-all delay-300 duration-1000 relative ${revealState === "REVEALED"
|
||||
? "opacity-0 w-0 h-0 overflow-hidden invisible"
|
||||
: "opacity-100"
|
||||
}`}
|
||||
|
||||
Reference in New Issue
Block a user