diff --git a/apps/web-admin/package.json b/apps/web-admin/package.json index 86225485..f8ecf6d4 100644 --- a/apps/web-admin/package.json +++ b/apps/web-admin/package.json @@ -22,12 +22,14 @@ "@mui/x-data-grid": "^6.19.3", "autoprefixer": "10.4.16", "axios": "^1.5.1", + "canvas": "^2.11.2", "clsx": "^2.0.0", "database": "workspace:*", "eslint": "8.56.0", "eslint-config-custom": "workspace:*", "eslint-config-next": "14.0.4", "framer-motion": "^11.0.6", + "konva": "^9.3.15", "next": "14.0.4", "papaparse": "^5.4.1", "postcss": "8.4.31", @@ -36,7 +38,9 @@ "react-dom": "18.2.0", "react-hook-form": "^7.49.0", "react-icons": "^5.0.1", + "react-konva": "^18.2.10", "react-qr-reader": "3.0.0-beta-1", + "react-remove-scroll": "^2.6.0", "sass": "^1.69.1", "zod": "^3.23.8" }, diff --git a/apps/web-admin/src/components/CertificateUploadBox.jsx b/apps/web-admin/src/components/CertificateUploadBox.jsx new file mode 100644 index 00000000..1120dd13 --- /dev/null +++ b/apps/web-admin/src/components/CertificateUploadBox.jsx @@ -0,0 +1,269 @@ +'use client'; // Ensure this is a Client Component + +import { useState, useRef, useEffect } from 'react'; +import { + Box, + Text, + AspectRatio, + VStack, + Button, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + Input, + Select, + useDisclosure, +} from '@chakra-ui/react'; +// import { Stage, Layer, Image as KonvaImage, Text as KonvaText } from 'react-konva'; +import dynamic from 'next/dynamic'; +// import { useEffect } from 'react'; + +const Stage = dynamic(() => import('react-konva').then((mod) => mod.Stage), { ssr: false }); +const Layer = dynamic(() => import('react-konva').then((mod) => mod.Layer), { ssr: false }); +const KonvaImage = dynamic(() => import('react-konva').then((mod) => mod.Image), { ssr: false }); +const KonvaText = dynamic(() => import('react-konva').then((mod) => mod.Text), { ssr: false }); + +function CertifcateUploadBox() { + const [imageSrc, setImageSrc] = useState(null); + const [konvaImage, setKonvaImage] = useState(null); + const fileInputRef = useRef(null); + const stageRef = useRef(null); + const parentRef = useRef(null); + const [parentSize, setParentSize] = useState({ width: 0, height: 0 }); + + const { isOpen, onOpen, onClose } = useDisclosure(); + const [selectedText, setSelectedText] = useState(null); + + const [texts, setTexts] = useState([]); // Store texts on canvas + + useEffect(() => { + if (parentRef.current) { + const updateSize = () => { + setParentSize({ + width: parentRef.current.offsetWidth, + height: parentRef.current.offsetHeight, + }); + }; + updateSize(); + window.addEventListener('resize', updateSize); + return () => { + window.removeEventListener('resize', updateSize); + }; + } + }, []); + + const handleBoxClick = () => { + if (!imageSrc) { + fileInputRef.current.click(); + } + }; + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + if (file.type.startsWith('image/')) { + const imageUrl = URL.createObjectURL(file); + setImageSrc(imageUrl); + + if (imageSrc) { + URL.revokeObjectURL(imageSrc); + } + } else { + alert('Please upload an image file.'); + } + } + }; + + useEffect(() => { + if (imageSrc) { + const image = new window.Image(); + image.src = imageSrc; + image.onload = () => { + setKonvaImage(image); + }; + image.onerror = () => { + console.error('Failed to load image.'); + }; + } + }, [imageSrc]); + + const handleResetBackground = () => { + setImageSrc(null); + setKonvaImage(null); + setTexts([]); + }; + + const handleDoubleClick = (e) => { + const stage = e.target.getStage(); + const pointerPosition = stage.getPointerPosition(); + setTexts((prevTexts) => [ + ...prevTexts, + { + id: `text-${texts.length + 1}`, + x: pointerPosition.x, + y: pointerPosition.y, + text: 'Double-clicked Text', + fontSize: 24, + draggable: true, + }, + ]); + }; + + const handleEditClick = (textObj) => { + setSelectedText(textObj); // Set selected text to edit + onOpen(); // Open modal + }; + + const handleModalSubmit = () => { + // Update the text object + setTexts((prevTexts) => + prevTexts.map((text) => (text.id === selectedText.id ? { ...text, ...selectedText } : text)), + ); + onClose(); // Close modal + }; + + return ( + + + + {imageSrc ? ( + <> + + + + {konvaImage && ( + + )} + {texts.map((textObj) => ( + + ))} + + + + + ) : ( + <> +
+ {/* SVG */} + + Upload an Image + +
+ + )} + +
+
+ {konvaImage && ( + + + + {texts.length > 0 && ( + + {texts.map((textObj) => ( + + + + ))} + + )} + + )} + + {/* Modal for editing text properties */} + + + + Edit Text Properties + + + setSelectedText({ ...selectedText, text: e.target.value })} + placeholder="Edit text" + mb={4} + /> + + setSelectedText({ ...selectedText, fontSize: parseInt(e.target.value) }) + } + placeholder="Font size" + mb={4} + /> + {/* You can add font and color selectors here */} + + + + + + + +
+ // + ); +} + +export default CertifcateUploadBox; diff --git a/apps/web-admin/src/pages/404.jsx b/apps/web-admin/src/pages/404.jsx index 795784cd..36893de3 100644 --- a/apps/web-admin/src/pages/404.jsx +++ b/apps/web-admin/src/pages/404.jsx @@ -1,7 +1,7 @@ // pages/404.tsx import { Box, Heading, Text, Button } from '@chakra-ui/react'; import Link from 'next/link'; -import { GetServerSideProps } from 'next'; +// import { GetServerSideProps } from 'next'; const Custom404 = () => { return ( diff --git a/apps/web-admin/src/pages/[orgId]/members/index.jsx b/apps/web-admin/src/pages/[orgId]/members/index.jsx index 27af929c..3948a57e 100644 --- a/apps/web-admin/src/pages/[orgId]/members/index.jsx +++ b/apps/web-admin/src/pages/[orgId]/members/index.jsx @@ -52,7 +52,7 @@ export default function OrganizationMembers() { return (