diff --git a/frontend/e2e/letter.spec.ts b/frontend/e2e/letter.spec.ts index 7ed1b5f..b4734a5 100644 --- a/frontend/e2e/letter.spec.ts +++ b/frontend/e2e/letter.spec.ts @@ -200,7 +200,7 @@ test.describe("Letter Drafting (Real Backend)", () => { await page.waitForTimeout(1500); // Click the letter to pull it out - await page.locator("#letter").click(); + await page.locator("#letter").click({ position: { x: 30, y: 15 } }); // Wait for reveal transition await expect(page.locator("#letter")).toBeHidden({ timeout: 20000 }); diff --git a/frontend/src/components/ui/EnvelopeReveal.tsx b/frontend/src/components/ui/EnvelopeReveal.tsx index 1e16296..ed0e1b7 100644 --- a/frontend/src/components/ui/EnvelopeReveal.tsx +++ b/frontend/src/components/ui/EnvelopeReveal.tsx @@ -1,5 +1,5 @@ import { WavesIcon } from "@phosphor-icons/react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import stamp from "../../assets/envelope/stamp.png"; import waxSeal from "../../assets/envelope/waxSeal.png"; @@ -7,18 +7,33 @@ export interface EnvelopeRevealProps { recipient?: string; date?: string; onRevealComplete: () => void; + ignite: boolean; } export function EnvelopeReveal({ recipient, date, onRevealComplete, + ignite, }: EnvelopeRevealProps) { const [revealLetter, setRevealLetter] = useState(false); const [isFlipped, setIsFlipped] = useState(false); + const [burn, setBurn] = useState<{ width: number; height: number }>({ + width: 0, + height: 0, + }); + const flapCheckbox = useRef(null); + useEffect(() => { + if (!ignite) return; + const burnInterval = setInterval(() => { + setBurn((prev) => ({ width: prev.width + 4, height: prev.height + 6 })); + }, 100); + return () => clearInterval(burnInterval); + }, [ignite]); + const handleClick = () => { if (revealLetter) return; setRevealLetter(true); @@ -28,7 +43,7 @@ export function EnvelopeReveal({ }; return ( -
+
@@ -68,7 +83,7 @@ export function EnvelopeReveal({ >
@@ -101,6 +116,17 @@ export function EnvelopeReveal({
+ {ignite && ( +
+
+
+ )} ); } diff --git a/frontend/src/components/ui/LogModal.tsx b/frontend/src/components/ui/LogModal.tsx index 017b1db..4540bd1 100644 --- a/frontend/src/components/ui/LogModal.tsx +++ b/frontend/src/components/ui/LogModal.tsx @@ -18,7 +18,7 @@ export const LogModal = ({ return status === "RESET" || !isOpen ? (
) : ( -
+
+
+
+
+
+ ); + } + useEffect(() => { if (!(sharingKey || masterKey)) { setError({ @@ -68,15 +173,19 @@ export default function Reader() { encrypted_dek, images, updated_at, + status, } = response.data; + if (status === "BURNED") + throw new Error("This letter has been burned."); + const cryptoUtils = new CryptoUtils(); const isShared = !!sharingKey; if (isShared && !encrypted_content) throw new Error("Content missing"); const isDecryptionKeyAvailable = encrypted_dek && masterKey; if (!(isShared || isDecryptionKeyAvailable)) - throw new Error("Auth required"); + throw new Error("Auth required: Decryption key is not available"); // Decrypt Metadata const decryptedMetadata = isShared @@ -198,16 +307,47 @@ export default function Reader() { : "opacity-100" }`} > - setRevealState("revealed")} - /> + {revealState === "sealed" && ( + setRevealState("revealed")} + ignite={ignite} + /> + )}
+ + {ignite && ( +
+

+ It is done +

+
+

+ May your soul find solace like your{" "} + unsaid words did. +

+
+ +
+
+ )} + setWarning(null)} @@ -232,9 +372,27 @@ export default function Reader() {

)}
+ + {isOwner && ( +
+ +
+ )}
)} + {showBurnModal && } +