From 0104703852dbea50ecdb905f9c8519a2907cded1 Mon Sep 17 00:00:00 2001 From: me Date: Wed, 6 May 2026 21:17:14 +0530 Subject: [PATCH] feat: add onboarding welcome letter overlay --- .../drawer/WelcomeLetterOverlay.tsx | 77 +++++++++++++ frontend/src/config/welcomeLetter.ts | 101 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 frontend/src/components/drawer/WelcomeLetterOverlay.tsx create mode 100644 frontend/src/config/welcomeLetter.ts diff --git a/frontend/src/components/drawer/WelcomeLetterOverlay.tsx b/frontend/src/components/drawer/WelcomeLetterOverlay.tsx new file mode 100644 index 0000000..a45d2b2 --- /dev/null +++ b/frontend/src/components/drawer/WelcomeLetterOverlay.tsx @@ -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(null); + + useEffect(() => { + if (revealState === "REVEALED" && canvasRef.current) { + const welcomeContent = getWelcomeContent(userName); + canvasRef.current.loadData(welcomeContent); + } + }, [revealState, userName]); + + return ( +
+
+ +
+ + {revealState === "SEALED" && ( + + setRevealState("REVEALED")} + ignite={false} + /> + + )} + +
+
+
+ +
+ +
+ +
+
+
+
+ ); +} diff --git a/frontend/src/config/welcomeLetter.ts b/frontend/src/config/welcomeLetter.ts new file mode 100644 index 0000000..f35ed58 --- /dev/null +++ b/frontend/src/config/welcomeLetter.ts @@ -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, + }; +}