From 84413674c7efb90e46dbe8cec90b013d58433744 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 22 Oct 2025 16:05:12 +0000 Subject: [PATCH] added fun confettit --- package-lock.json | 65 ++++++++++++++++++ package.json | 2 + .../[slug]/components/BookSurveyModal.tsx | 10 +++ .../[slug]/components/BookingSuccessToast.tsx | 66 +++++++++++++++++++ .../components/propertyTableColumns.tsx | 11 ++++ 5 files changed, 154 insertions(+) create mode 100644 src/app/portfolio/[slug]/components/BookingSuccessToast.tsx diff --git a/package-lock.json b/package-lock.json index 13256ff..76492d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "drizzle-orm": "^0.44.5", "esbuild": "^0.25.8", "eslint-config-next": "13.4.3", + "framer-motion": "^12.23.24", "lucide-react": "^0.233.0", "next": "^15.4.2", "next-auth": "^4.22.1", @@ -50,6 +51,7 @@ "pg": "^8.11.1", "postcss": "^8.5.6", "react": "18.3.1", + "react-confetti": "^6.4.0", "react-dom": "18.3.1", "react-hook-form": "^7.53.2", "tailwind-merge": "^1.13.2", @@ -9615,6 +9617,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", + "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -11292,6 +11321,21 @@ "dev": true, "license": "MIT" }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -12656,6 +12700,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-confetti": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.4.0.tgz", + "integrity": "sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==", + "license": "MIT", + "dependencies": { + "tween-functions": "^1.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-day-picker": { "version": "8.10.1", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", @@ -14372,6 +14431,12 @@ "node": "*" } }, + "node_modules/tween-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", + "integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==", + "license": "BSD" + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", diff --git a/package.json b/package.json index ee18b47..922bc3e 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "drizzle-orm": "^0.44.5", "esbuild": "^0.25.8", "eslint-config-next": "13.4.3", + "framer-motion": "^12.23.24", "lucide-react": "^0.233.0", "next": "^15.4.2", "next-auth": "^4.22.1", @@ -56,6 +57,7 @@ "pg": "^8.11.1", "postcss": "^8.5.6", "react": "18.3.1", + "react-confetti": "^6.4.0", "react-dom": "18.3.1", "react-hook-form": "^7.53.2", "tailwind-merge": "^1.13.2", diff --git a/src/app/portfolio/[slug]/components/BookSurveyModal.tsx b/src/app/portfolio/[slug]/components/BookSurveyModal.tsx index 9d021d0..5a5f112 100644 --- a/src/app/portfolio/[slug]/components/BookSurveyModal.tsx +++ b/src/app/portfolio/[slug]/components/BookSurveyModal.tsx @@ -19,6 +19,7 @@ interface BookSurveyModalProps { propertyId: bigint; portfolioId: bigint; address: string; + onSuccess?: () => void; // ✅ fix: properly declare optional callback } export default function BookSurveyModal({ @@ -27,6 +28,7 @@ export default function BookSurveyModal({ propertyId, portfolioId, address, + onSuccess, // ✅ fix: remove “?:” here, we already declared it optional in interface }: BookSurveyModalProps) { const [formData, setFormData] = useState({ dealname: "", @@ -54,6 +56,7 @@ export default function BookSurveyModal({ console.log("✅ Deal created successfully:", data); console.log("HUBSPOT DEAL ID MADE", data.dealId); onOpenChange(false); + if (onSuccess) onSuccess(); // 👈 trigger confetti toast }, onError: (error) => { console.error("❌ Deal creation failed:", error); @@ -119,5 +122,12 @@ export default function BookSurveyModal({ + ); } + + +// TODO: check if bug is happening and why. +// TODO: Ask khalim what we want to do, maybe a list of Hubspot DB record? if someone presses twice, currently just updates +// TODO: Make a sexy toast that the deal has been processed +// TODO: Show khalim a demo and other clean ups for good user experience \ No newline at end of file diff --git a/src/app/portfolio/[slug]/components/BookingSuccessToast.tsx b/src/app/portfolio/[slug]/components/BookingSuccessToast.tsx new file mode 100644 index 0000000..7b9410d --- /dev/null +++ b/src/app/portfolio/[slug]/components/BookingSuccessToast.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import Confetti from "react-confetti"; +import { CheckCircle } from "lucide-react"; + +interface BookingSuccessToastProps { + show: boolean; + onClose: () => void; + message?: string; + subtext?: string; +} + +export default function BookingSuccessToast({ + show, + onClose, + message = "Booking Confirmed!", + subtext = "You’re all set. 🎉", +}: BookingSuccessToastProps) { + const [confetti, setConfetti] = useState(false); + + useEffect(() => { + if (show) { + setConfetti(true); + const timer = setTimeout(() => { + setConfetti(false); + onClose(); + }, 4000); + return () => clearTimeout(timer); + } + }, [show, onClose]); + + return ( + <> + + {show && ( + +
+ +
+
+

{message}

+

{subtext}

+
+
+ )} +
+ + {confetti && ( + + )} + + ); +} diff --git a/src/app/portfolio/[slug]/components/propertyTableColumns.tsx b/src/app/portfolio/[slug]/components/propertyTableColumns.tsx index 8b8d780..a3e3533 100644 --- a/src/app/portfolio/[slug]/components/propertyTableColumns.tsx +++ b/src/app/portfolio/[slug]/components/propertyTableColumns.tsx @@ -22,6 +22,7 @@ import { PropertyWithRelations, } from "@/app/db/schema/property"; import BookSurveyModal from "./BookSurveyModal"; +import BookingSuccessToast from "./BookingSuccessToast"; import { useState } from "react"; @@ -218,6 +219,7 @@ export const columns: ColumnDef[] = [ id: "actions", cell: ({ row }) => { const [openModal, setOpenModal] = useState(false); + const [showToast, setShowToast] = useState(false); const property = row.original; const propertyId = property.id; const portfolioId = property.portfolioId; @@ -271,8 +273,17 @@ export const columns: ColumnDef[] = [ propertyId={propertyId} portfolioId={portfolioId} address={"ask khalim"} + onSuccess={() => setShowToast(true)} /> )} + + {/* 💥 Toast */} + setShowToast(false)} + message="Deal Created Successfully!" + subtext="Your HubSpot deal is ready. 🎉" + /> ); },