refactor: reduce complexity

This commit is contained in:
me
2026-05-08 10:33:11 +05:30
parent 26cf95c78b
commit 7e79c6ca8b
2 changed files with 772 additions and 767 deletions
+66 -64
View File
@@ -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"
}`}
+7 -4
View File
@@ -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"
}`}