refactor: lint formatting and fixes #6
@@ -11,6 +11,7 @@ import {
|
|||||||
useParams,
|
useParams,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { api } from "../api/apiClient";
|
import { api } from "../api/apiClient";
|
||||||
|
import type { LetterResponseData } from "../api/response";
|
||||||
import {
|
import {
|
||||||
type CanvasStyle,
|
type CanvasStyle,
|
||||||
type CanvasTools,
|
type CanvasTools,
|
||||||
@@ -26,7 +27,6 @@ import DateDisplay from "../components/ui/DateDisplay";
|
|||||||
import { LogModal } from "../components/ui/LogModal";
|
import { LogModal } from "../components/ui/LogModal";
|
||||||
import { Modal } from "../components/ui/Modal";
|
import { Modal } from "../components/ui/Modal";
|
||||||
import { Navbar } from "../components/ui/Navbar";
|
import { Navbar } from "../components/ui/Navbar";
|
||||||
|
|
||||||
import { endpoints } from "../config/endpoints";
|
import { endpoints } from "../config/endpoints";
|
||||||
import { PATHS } from "../config/routes";
|
import { PATHS } from "../config/routes";
|
||||||
import { useKeyStore } from "../store/useKeyStore";
|
import { useKeyStore } from "../store/useKeyStore";
|
||||||
@@ -116,26 +116,11 @@ export default function Editor() {
|
|||||||
justSavedRef.current = false;
|
justSavedRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const loadExistingLetter = async () => {
|
const decryptAndLoadLetter = async (
|
||||||
setIsInitialLoading(true);
|
letterData: LetterResponseData,
|
||||||
|
masterKey: CryptoKey,
|
||||||
|
) => {
|
||||||
const cryptoUtils = new CryptoUtils();
|
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(
|
const metadata = await cryptoUtils.decryptMetadata(
|
||||||
{
|
{
|
||||||
encrypted_content: letterData.encrypted_metadata,
|
encrypted_content: letterData.encrypted_metadata,
|
||||||
@@ -167,8 +152,7 @@ export default function Editor() {
|
|||||||
if (isPartialFailure) {
|
if (isPartialFailure) {
|
||||||
setDecryptionStatus({
|
setDecryptionStatus({
|
||||||
status: "WARN",
|
status: "WARN",
|
||||||
message:
|
message: "Failed to decrypt some elements. Please check the render.",
|
||||||
"Failed to decrypt some elements. Please check the render.",
|
|
||||||
log: errors.toString(),
|
log: errors.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -176,11 +160,30 @@ export default function Editor() {
|
|||||||
if (canvasRef.current) {
|
if (canvasRef.current) {
|
||||||
await canvasRef.current.loadData(canvasDataWithDecryptedImages);
|
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({
|
setDecryptionStatus({
|
||||||
status: "ERROR",
|
status: "ERROR",
|
||||||
message: "Failed to decrypt letter. Please try again later.",
|
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 {
|
} finally {
|
||||||
setIsInitialLoading(false);
|
setIsInitialLoading(false);
|
||||||
@@ -242,76 +245,79 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async (
|
const getRequestData = async (
|
||||||
status: "SEALED" | "DRAFT" | "VAULT",
|
targetId: string,
|
||||||
|
status: string,
|
||||||
vaultDate?: Date,
|
vaultDate?: Date,
|
||||||
): Promise<void> => {
|
): Promise<FormData> => {
|
||||||
setSealBtnClicked(false);
|
|
||||||
|
|
||||||
let targetId = public_id || letterIdRef.current;
|
|
||||||
if (!targetId) {
|
|
||||||
targetId = crypto.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveOverlay === "SAVING" || !masterKey) return;
|
|
||||||
|
|
||||||
setSaveOverlay("SAVING");
|
|
||||||
setShowSaveOverlay(true);
|
|
||||||
|
|
||||||
const cryptoUtils = new CryptoUtils();
|
const cryptoUtils = new CryptoUtils();
|
||||||
await cryptoUtils.initialize();
|
await cryptoUtils.initialize();
|
||||||
|
|
||||||
try {
|
const canvasData = (await canvasRef.current?.getData()) || { objects: [] };
|
||||||
const canvasData = (await canvasRef.current?.getData()) || {
|
|
||||||
objects: [],
|
|
||||||
};
|
|
||||||
const canvasImages = canvasRef.current?.getImages() || [];
|
const canvasImages = canvasRef.current?.getImages() || [];
|
||||||
|
|
||||||
const { encryptedImageFiles, encryptedCanvasData } =
|
const { encryptedImageFiles, encryptedCanvasData } =
|
||||||
await encryptCanvasImages(
|
await encryptCanvasImages(
|
||||||
canvasData,
|
canvasData,
|
||||||
canvasImages,
|
canvasImages,
|
||||||
masterKey,
|
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||||
|
masterKey!,
|
||||||
cryptoUtils,
|
cryptoUtils,
|
||||||
);
|
);
|
||||||
|
|
||||||
const encrypted_letter = await cryptoUtils.encryptLetter(
|
const encrypted_letter = await cryptoUtils.encryptLetter(
|
||||||
JSON.stringify(encryptedCanvasData),
|
JSON.stringify(encryptedCanvasData),
|
||||||
masterKey,
|
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||||
|
masterKey!,
|
||||||
);
|
);
|
||||||
|
|
||||||
const encrypted_metadata = await cryptoUtils.encryptMetadata(
|
const encrypted_metadata = await cryptoUtils.encryptMetadata(
|
||||||
{ recipient, tags: [] },
|
{ recipient, tags: [] },
|
||||||
masterKey,
|
// biome-ignore lint/style/noNonNullAssertion: masterkey can never be null here
|
||||||
|
masterKey!,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (status === "VAULT") {
|
if (status === "VAULT") {
|
||||||
const finalDate = vaultDate || unlockDate;
|
const finalDate = vaultDate || unlockDate;
|
||||||
formData.append("type", "VAULT");
|
formData.append("type", "VAULT");
|
||||||
if (finalDate) {
|
if (finalDate) formData.append("unlock_at", finalDate.toISOString());
|
||||||
formData.append("unlock_at", finalDate.toISOString());
|
|
||||||
}
|
|
||||||
formData.append("status", "SEALED");
|
formData.append("status", "SEALED");
|
||||||
} else {
|
} else {
|
||||||
formData.append("type", "KEPT");
|
formData.append("type", "KEPT");
|
||||||
formData.append("status", status);
|
formData.append("status", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append("public_id", targetId);
|
formData.append("public_id", targetId);
|
||||||
formData.append("encrypted_content", encrypted_letter.encrypted_content);
|
formData.append("encrypted_content", encrypted_letter.encrypted_content);
|
||||||
formData.append("encrypted_dek", encrypted_letter.encrypted_dek);
|
formData.append("encrypted_dek", encrypted_letter.encrypted_dek);
|
||||||
formData.append(
|
formData.append("encrypted_metadata", encrypted_metadata.encrypted_content);
|
||||||
"encrypted_metadata",
|
|
||||||
encrypted_metadata.encrypted_content,
|
|
||||||
);
|
|
||||||
|
|
||||||
encryptedImageFiles.forEach((blob, filename) => {
|
encryptedImageFiles.forEach((blob, filename) => {
|
||||||
formData.append("image_files", blob, filename);
|
formData.append("image_files", blob, filename);
|
||||||
});
|
});
|
||||||
|
|
||||||
await api.put(`${endpoints.LETTERS}${targetId}/`, formData);
|
return formData;
|
||||||
justSavedRef.current = true;
|
};
|
||||||
|
|
||||||
|
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) {
|
if (!public_id) {
|
||||||
letterIdRef.current = targetId;
|
letterIdRef.current = targetId;
|
||||||
navigate(PATHS.write(targetId), { replace: true });
|
navigate(PATHS.write(targetId), { replace: true });
|
||||||
@@ -326,7 +332,7 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
setSaveOverlay("SAVED");
|
setSaveOverlay("SAVED");
|
||||||
setShowSaveOverlay(true);
|
setShowSaveOverlay(true);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
setSaveOverlay("ERROR");
|
setSaveOverlay("ERROR");
|
||||||
setShowSaveOverlay(true);
|
setShowSaveOverlay(true);
|
||||||
}
|
}
|
||||||
@@ -337,8 +343,7 @@ export default function Editor() {
|
|||||||
<Navbar
|
<Navbar
|
||||||
child={
|
child={
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 ${
|
className={`flex items-center gap-2 ${isSaveDatePulsing ? "animate-pulse" : ""
|
||||||
isSaveDatePulsing ? "animate-pulse" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="text-xxs text-neutral-content/30 flex-col justify-end leading-none text-right">
|
<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" && (
|
{saveOverlay === "SAVING" && (
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
className={`alert text-center alert-neutral shadow-lg transition-all ease-in-out duration-2000 ${
|
className={`alert text-center alert-neutral shadow-lg transition-all ease-in-out duration-2000 ${showSaveOverlay
|
||||||
showSaveOverlay
|
|
||||||
? "opacity-100 scale-100 translate-y-0"
|
? "opacity-100 scale-100 translate-y-0"
|
||||||
: "opacity-0 scale-95 translate-y-1"
|
: "opacity-0 scale-95 translate-y-1"
|
||||||
}`}
|
}`}
|
||||||
@@ -410,8 +414,7 @@ export default function Editor() {
|
|||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
data-testid="save-success-toast"
|
data-testid="save-success-toast"
|
||||||
className={`alert alert-success shadow-lg transition-all ease-in-out duration-2000 ${
|
className={`alert alert-success shadow-lg transition-all ease-in-out duration-2000 ${showSaveOverlay
|
||||||
showSaveOverlay
|
|
||||||
? "opacity-100 scale-100 translate-y-0"
|
? "opacity-100 scale-100 translate-y-0"
|
||||||
: "opacity-0 scale-95 translate-y-1"
|
: "opacity-0 scale-95 translate-y-1"
|
||||||
}`}
|
}`}
|
||||||
@@ -424,8 +427,7 @@ export default function Editor() {
|
|||||||
{saveOverlay === "ERROR" && (
|
{saveOverlay === "ERROR" && (
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
className={`alert alert-error shadow-lg transition-all duration-300 ${
|
className={`alert alert-error shadow-lg transition-all duration-300 ${showSaveOverlay
|
||||||
showSaveOverlay
|
|
||||||
? "opacity-100 scale-100 translate-y-0"
|
? "opacity-100 scale-100 translate-y-0"
|
||||||
: "opacity-0 scale-95 translate-y-1"
|
: "opacity-0 scale-95 translate-y-1"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ export default function Reader() {
|
|||||||
const key = await cryptoUtils.extractSharingKey(encryptedDek, masterKey);
|
const key = await cryptoUtils.extractSharingKey(encryptedDek, masterKey);
|
||||||
try {
|
try {
|
||||||
await api.patch(`${endpoints.LETTERS}${public_id}/`, { type: "SENT" });
|
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 {
|
} finally {
|
||||||
setShareLink(`${window.location.origin}${PATHS.read(public_id)}#${key}`);
|
setShareLink(`${window.location.origin}${PATHS.read(public_id)}#${key}`);
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,10 @@ export default function Reader() {
|
|||||||
await api.patch(`${endpoints.LETTERS}${public_id}/`, {
|
await api.patch(`${endpoints.LETTERS}${public_id}/`, {
|
||||||
status: "BURNED",
|
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 {
|
} finally {
|
||||||
setIsBurning(false);
|
setIsBurning(false);
|
||||||
setShowBurnModal(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">
|
<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="fixed inset-0 bg-vig pointer-events-none z-0" />
|
||||||
<div
|
<div
|
||||||
className={`transition-all delay-300 duration-1000 relative ${
|
className={`transition-all delay-300 duration-1000 relative ${revealState === "REVEALED"
|
||||||
revealState === "REVEALED"
|
|
||||||
? "opacity-0 w-0 h-0 overflow-hidden invisible"
|
? "opacity-0 w-0 h-0 overflow-hidden invisible"
|
||||||
: "opacity-100"
|
: "opacity-100"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user