feat: ux enhancement for envelope reveal

This commit is contained in:
ramvignesh-b
2026-04-21 04:21:59 +05:30
parent b5d55bd258
commit 4c204b0a80
7 changed files with 102 additions and 55 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

+79 -28
View File
@@ -1,4 +1,7 @@
import { useState } from "react";
import { WavesIcon } from "@phosphor-icons/react";
import { useRef, useState } from "react";
import stamp from "../../assets/envelope/stamp.png";
import waxSeal from "../../assets/envelope/waxSeal.png";
export interface EnvelopeRevealProps {
recipient?: string;
@@ -12,45 +15,93 @@ export function EnvelopeReveal({
onRevealComplete,
}: EnvelopeRevealProps) {
const [revealLetter, setRevealLetter] = useState(false);
const [isFlipped, setIsFlipped] = useState(false);
const flapCheckbox = useRef<HTMLInputElement>(null);
const handleClick = () => {
setRevealLetter(true);
setTimeout(() => {
onRevealComplete();
}, 1000);
}, 2500);
};
return (
<div className="max-w-4xl m-8 mx-auto space-y-8 h-screen w-screen flex items-center justify-center">
<div className="flex items-center justify-center transition-all duration-1000">
<div className="h-screen mx-auto items-center flex justify-center">
<div className="perspective-distant scale-80 duration-1000 transition-all animate-[pulse_2s_linear_1]">
<div
id="env-top"
className="z-4 transition-transform duration-2000 absolute peer h-40 w-54 -mt-30 bg-base-200 mask mask-triangle-2 scale-x-234 has-checked:scale-y-[-1] has-checked:-translate-y-full has-checked:z-1 has-checked:duration-1000"
className={`relative h-70 w-105 transform-3d transition-transform duration-2000 ${isFlipped ? "rotate-y-180" : ""}`}
>
<input
type="checkbox"
className=" transition-all checkbox duration-1000 absolute h-full w-full bg-transparent z-100 text-transparent"
/>
</div>
<div
id="letter"
className={`max-w-4xl m-8 mx-auto space-y-8 transition-all peer-has-checked:duration-2000 duration-500 h-65 w-105 bg-paper peer-has-checked:mb-24 hover:mb-54 cursor-pointer ${revealLetter ? "w-screen h-screen z-101" : "peer-has-checked:z-1"}`}
onClick={handleClick}
></div>
<div className=" flex backface-hidden rotate-y-180 justify-center transition-all duration-1000">
<div
id="env-top"
className="z-4 delay-500 transition-all duration-2000 absolute peer h-40 w-54 mt-0 bg-base-200 mask mask-triangle-2 scale-x-234 has-checked:scale-y-[-1] has-checked:-translate-y-full has-checked:z-1 has-checked:duration-1000"
>
<input
type="checkbox"
className="transition checkbox absolute h-full w-full text-transparent bg-transparent z-100"
ref={flapCheckbox}
/>
</div>
<img
className={
"translate-y-24 delay-2000 absolute z-6 peer-has-checked:opacity-0 peer-has-checked:delay-0 transition-opacity duration-1500 cursor-pointer"
}
src={waxSeal}
alt="Seal"
onClick={() => flapCheckbox.current?.click()}
/>
<button
id="letter"
className={`absolute mx-auto transition-all peer-has-checked:delay-800 peer-has-checked:duration-1000 duration-1000 mt-2 h-63 w-105 bg-paper peer-has-checked:-mt-12 hover:-mt-24 cursor-pointer ${revealLetter ? "duration-1000 peer-has-checked:duration-2000 w-screen h-screen z-101 -translate-y-90" : "peer-has-checked:z-1"}`}
onClick={handleClick}
></button>
<div
id="env-right"
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-3 -mr-48 z-3"
></div>
<div
id="env-left"
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-4 -ml-48 z-3"
></div>
<div
id="env-bottom"
className="absolute h-70 w-45 bg-base-200 mask mask-triangle-2 scale-y-[-1] -mb-31 scale-x-240 z-3"
></div>
<div
id="env-right"
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-3 -mr-48 z-3"
></div>
<div
id="env-left"
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-4 -ml-48 z-3"
></div>
<button
id="env-bottom"
className="absolute h-70 w-45 bg-base-200 mask mask-triangle-2 scale-y-[-1] mt-15 scale-x-240 z-3"
onClick={() => setIsFlipped((prev) => !prev)}
></button>
</div>
<div
className="p-10 absolute inset-0 backface-hidden w-110 bg-base-200 z-99 rounded-md -translate-x-2"
onClick={() => setIsFlipped((prev) => !prev)}
>
<span className={"text-neutral-content/60 font-xs font-display"}>
to
</span>
<h1 className="text-3xl font-bold text-base-content">
{recipient}
</h1>
<p className="text-base-content/60 font-display mt-8">{date}</p>
<img
src={stamp}
alt={"stamp"}
className={
"z-0 rotate-6 opacity-80 text-accent absolute mt-0 mr-1 top-4 right-0"
}
/>
<WavesIcon
className={"absolute mt-0 mr-12 top-18 right-8 text-primary"}
size={50}
/>
<WavesIcon
className={"absolute mt-0 mr-4 top-18 right-8 text-primary"}
size={50}
/>
</div>
</div>
</div>
</div>
);
}
+3 -3
View File
@@ -12,7 +12,7 @@ import { LogModal } from "../components/ui/LogModal";
import { endpoints } from "../config/endpoints";
import { useKeyStore } from "../store/useKeyStore";
import { CryptoUtils } from "../utils/crypto";
import { formatRelativeDate } from "../utils/dateFormat";
import { formatDate } from "../utils/dateFormat";
import {
decryptCanvasImages,
decryptCanvasImagesWithSharingKey,
@@ -215,10 +215,10 @@ export default function Reader() {
className={`transition-all duration-1000 relative ${revealState === "revealed" ? "opacity-0 w-0 h-0" : "opacity-100"}`}
>
<EnvelopeReveal
recipient={metadata?.recipient}
recipient={metadata?.recipient || "Someone dear"}
date={
metadata?.updated_at
? formatRelativeDate(new Date(metadata.updated_at))
? formatDate(new Date(metadata.updated_at))
: undefined
}
onRevealComplete={() => setRevealState("revealed")}
+9
View File
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { formatDate } from "../../../../../../../../../home/atom/Documents/code/pi_ku/frontend/src/utils/dateFormat";
import { formatRelativeDate } from "./dateFormat";
describe("formatRelativeDate", () => {
@@ -35,3 +36,11 @@ describe("formatRelativeDate", () => {
expect(result).toBe("Apr 1, 2026, 6:45 PM");
});
});
describe("formatDate", () => {
it("should format a new date as mmm dd, yyyy", () => {
const result = formatDate("2026-04-01T10:15:00Z");
expect(result).toBe("April 1, 2026");
});
});
+11
View File
@@ -7,6 +7,10 @@ const dateTimeFormatter = new Intl.DateTimeFormat("en-US", {
timeStyle: "short",
});
const dateFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "long",
});
const rtf = new Intl.RelativeTimeFormat("en-US", {
numeric: "auto",
});
@@ -50,3 +54,10 @@ export function formateRelativeDateWithoutTime(input: Date | string | number) {
return date.toDateString();
}
export function formatDate(input: Date | string | number) {
if (!input) return "";
const date = new Date(input);
return dateFormatter.format(date);
}