feat/welcome-letter integration #2
@@ -0,0 +1,77 @@
|
|||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { getWelcomeContent } from "../../config/welcomeLetter";
|
||||||
|
import { formatDate } from "../../utils/dateFormat";
|
||||||
|
import { type CanvasTools, ComposeCanvas } from "../editor/ComposeCanvas";
|
||||||
|
import { EnvelopeReveal } from "../reader/EnvelopeReveal";
|
||||||
|
|
||||||
|
interface WelcomeLetterOverlayProps {
|
||||||
|
onComplete: () => void;
|
||||||
|
userName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WelcomeLetterOverlay({
|
||||||
|
onComplete,
|
||||||
|
userName,
|
||||||
|
}: WelcomeLetterOverlayProps) {
|
||||||
|
const [revealState, setRevealState] = useState<"SEALED" | "REVEALED">(
|
||||||
|
"SEALED",
|
||||||
|
);
|
||||||
|
const canvasRef = useRef<CanvasTools>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (revealState === "REVEALED" && canvasRef.current) {
|
||||||
|
const welcomeContent = getWelcomeContent(userName);
|
||||||
|
canvasRef.current.loadData(welcomeContent);
|
||||||
|
}
|
||||||
|
}, [revealState, userName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-30 backdrop-blur-3xl flex flex-col items-center justify-center p-4 md:p-8 overflow-x-hidden">
|
||||||
|
<div className="fixed inset-0 bg-vig pointer-events-none z-0" />
|
||||||
|
|
||||||
|
<div className="w-full max-w-4xl z-10 flex flex-col items-center">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{revealState === "SEALED" && (
|
||||||
|
<motion.div
|
||||||
|
key="envelope"
|
||||||
|
initial={{ scale: 0.5, opacity: 0 }}
|
||||||
|
animate={{ scale: 0.8, opacity: 1 }}
|
||||||
|
exit={{
|
||||||
|
scale: 1,
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.5, ease: "easeOut" },
|
||||||
|
}}
|
||||||
|
transition={{ duration: 4, delay: 1 }}
|
||||||
|
>
|
||||||
|
<EnvelopeReveal
|
||||||
|
recipient={userName}
|
||||||
|
date={formatDate(new Date())}
|
||||||
|
onRevealComplete={() => setRevealState("REVEALED")}
|
||||||
|
ignite={false}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<div
|
||||||
|
className={`w-full space-y-8 py-12 ${revealState === "REVEALED" ? "block" : "hidden"}`}
|
||||||
|
>
|
||||||
|
<div className="bg-paper shadow-warm rounded-sm overflow-hidden mx-auto max-w-180">
|
||||||
|
<div className="p-1 md:p-2 bg-base-content/5 opacity-10 pointer-events-none absolute inset-0 z-10" />
|
||||||
|
<ComposeCanvas ref={canvasRef} readOnly />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center mt-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onComplete}
|
||||||
|
className="btn btn-accent opacity-80 px-12 shadow-lg"
|
||||||
|
>
|
||||||
|
I'll see you
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import type { CanvasJSON } from "../components/editor/ComposeCanvas";
|
||||||
|
|
||||||
|
export function getWelcomeLetterContent(userName: string): CanvasJSON {
|
||||||
|
return {
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 500,
|
||||||
|
fontFamily: "Kavivanar",
|
||||||
|
fontStyle: "normal",
|
||||||
|
lineHeight: 1.5,
|
||||||
|
text: `\nDear ${userName}, \n\nYou made it this far, which means something already brought you here. \nA name, maybe. A feeling you haven't been able to shake. Something you typed and deleted too many times to count.\n\nMost people carry it quietly. They tell themselves it doesn't matter anymore, or that too much time has passed, or that the other person wouldn't understand anyway. And maybe they're right. \n\nBut the thing is — the unsaid thing doesn't really care about any of that. \nIt just stays.\n\nSo here you are.\n\nYou don't have to know what you want to say yet. \nYou don't have to have it figured out — who it's for, or why it still matters, or what you're hoping will happen after. \n\nA lot of letters written here start without any of that. They find their way.\n\nTake your time. \nNo one's watching. \n\nWhen you're ready, write a letter.\n\nSometimes the wrong train takes you to the right station.\n- S.F.`,
|
||||||
|
charSpacing: 0,
|
||||||
|
textAlign: "left",
|
||||||
|
styles: [],
|
||||||
|
pathStartOffset: 0,
|
||||||
|
pathSide: "left",
|
||||||
|
pathAlign: "baseline",
|
||||||
|
underline: false,
|
||||||
|
overline: false,
|
||||||
|
linethrough: false,
|
||||||
|
textBackgroundColor: "",
|
||||||
|
direction: "ltr",
|
||||||
|
textDecorationThickness: 66.667,
|
||||||
|
minWidth: 20,
|
||||||
|
splitByGrapheme: false,
|
||||||
|
type: "Textbox",
|
||||||
|
version: "7.2.0",
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
|
left: 36,
|
||||||
|
top: 36,
|
||||||
|
width: 608,
|
||||||
|
height: 813.6,
|
||||||
|
fill: "#111e67",
|
||||||
|
stroke: null,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeDashArray: null,
|
||||||
|
strokeLineCap: "butt",
|
||||||
|
strokeDashOffset: 0,
|
||||||
|
strokeLineJoin: "miter",
|
||||||
|
strokeUniform: false,
|
||||||
|
strokeMiterLimit: 4,
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
angle: 0,
|
||||||
|
flipX: false,
|
||||||
|
flipY: false,
|
||||||
|
opacity: 1,
|
||||||
|
shadow: null,
|
||||||
|
visible: true,
|
||||||
|
backgroundColor: "",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
paintFirst: "fill",
|
||||||
|
globalCompositeOperation: "source-over",
|
||||||
|
skewX: 0,
|
||||||
|
skewY: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cropX: 0,
|
||||||
|
cropY: 0,
|
||||||
|
type: "Image",
|
||||||
|
version: "7.2.0",
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
|
left: 298.4065,
|
||||||
|
top: 660.2853,
|
||||||
|
width: 512,
|
||||||
|
height: 400,
|
||||||
|
fill: "rgb(0,0,0)",
|
||||||
|
stroke: null,
|
||||||
|
strokeWidth: 0,
|
||||||
|
strokeDashArray: null,
|
||||||
|
strokeLineCap: "butt",
|
||||||
|
strokeDashOffset: 0,
|
||||||
|
strokeLineJoin: "miter",
|
||||||
|
strokeUniform: false,
|
||||||
|
strokeMiterLimit: 4,
|
||||||
|
scaleX: 0.4753,
|
||||||
|
scaleY: 0.4753,
|
||||||
|
angle: 355.5436,
|
||||||
|
flipX: false,
|
||||||
|
flipY: false,
|
||||||
|
opacity: 1,
|
||||||
|
shadow: null,
|
||||||
|
visible: true,
|
||||||
|
backgroundColor: "",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
paintFirst: "fill",
|
||||||
|
globalCompositeOperation: "source-over",
|
||||||
|
skewX: 0,
|
||||||
|
skewY: 0,
|
||||||
|
src: "/screenshots/train.png",
|
||||||
|
crossOrigin: null,
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
canvasWidth: 680,
|
||||||
|
canvasHeight: 900,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user