Skip to content

Commit

Permalink
Certificate Template Upload and fixed build errors (#465)
Browse files Browse the repository at this point in the history
* Try: fix of merge conflict

* initialized the canvas in certifcate (#448)

* Fixing build errors

* Trying to fic build errors 2

* Fixing build errors 4

* Fixing build errors 5

* Fixing build errors 6

---------

Co-authored-by: Amal Varghese <[email protected]>
  • Loading branch information
subru-37 and codewizard-2004 authored Sep 23, 2024
1 parent 61066aa commit 9c3f32e
Show file tree
Hide file tree
Showing 7 changed files with 14,414 additions and 11 deletions.
4 changes: 4 additions & 0 deletions apps/web-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
269 changes: 269 additions & 0 deletions apps/web-admin/src/components/CertificateUploadBox.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box
display="flex"
justifyContent="center"
gap="10px"
alignItems="top"
height="100vh"
id="outer-box"
>
<AspectRatio ratio={16 / 9} width="70%" height="70%" maxW="100%" id="aspect-box">
<Box
borderWidth="2px"
borderStyle="dashed"
borderColor="black"
borderRadius="xl"
display="flex"
gap="10px"
flexDirection="column"
alignItems="center"
justifyContent="center"
onClick={handleBoxClick}
cursor={!konvaImage ? 'pointer' : 'auto'}
ref={parentRef}
>
{imageSrc ? (
<>
<Box width="100%" height="100%" position="absolute" top={0} left={0}>
<Stage
width={parentSize.width}
height={parentSize.height}
onDblClick={handleDoubleClick}
>
<Layer>
{konvaImage && (
<KonvaImage
image={konvaImage}
width={parentSize.width || 500}
height={parentSize.height || 500}
x={0}
y={0}
/>
)}
{texts.map((textObj) => (
<KonvaText
key={textObj.id}
x={textObj.x}
y={textObj.y}
text={textObj.text}
fontSize={textObj.fontSize}
draggable={textObj.draggable}
/>
))}
</Layer>
</Stage>
</Box>
</>
) : (
<>
<div>
{/* SVG */}
<Text fontSize="lg" color="gray.500">
Upload an Image
</Text>
</div>
</>
)}
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
style={{ display: 'none' }}
accept="image/*"
/>
</Box>
</AspectRatio>
{konvaImage && (
<VStack>
<Button colorScheme="red" size="sm" marginTop={10} onClick={handleResetBackground}>
Reset Canvas
</Button>

{texts.length > 0 && (
<VStack width="100%">
{texts.map((textObj) => (
<Box width="100%" key={textObj.id}>
<Button
id="edit-button"
colorScheme="teal"
size="sm"
width="100%"
onClick={() => handleEditClick(textObj)}
>
{textObj.id}
</Button>
</Box>
))}
</VStack>
)}
</VStack>
)}

{/* Modal for editing text properties */}
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Text Properties</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Input
value={selectedText?.text || ''}
onChange={(e) => setSelectedText({ ...selectedText, text: e.target.value })}
placeholder="Edit text"
mb={4}
/>
<Input
type="number"
value={selectedText?.fontSize || 24}
onChange={(e) =>
setSelectedText({ ...selectedText, fontSize: parseInt(e.target.value) })
}
placeholder="Font size"
mb={4}
/>
{/* You can add font and color selectors here */}
</ModalBody>
<ModalFooter>
<Button colorScheme="teal" onClick={handleModalSubmit}>
Save
</Button>
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Box>
// <Box></Box>
);
}

export default CertifcateUploadBox;
2 changes: 1 addition & 1 deletion apps/web-admin/src/pages/404.jsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion apps/web-admin/src/pages/[orgId]/members/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function OrganizationMembers() {
return (
<DashboardLayout
pageTitle="Members"
previousPage={`/organizations/${orgId}`}
previousPage={`/${orgId}`}
headerButton={
<>
<Button onClick={onOpen} isLoading={loading}>
Expand Down
21 changes: 14 additions & 7 deletions apps/web-admin/src/pages/[orgId]/mycertificates/index.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import React from 'react';
'use client';
import { useRouter } from 'next/router';
import DashboardLayout from '@/layouts/DashboardLayout';
import ComingSoon from '@/components/ComingSoon';
// import CertifcateUploadBox from '@/components/CertificateUploadBox';
// import ComingSoon from '@/components/ComingSoon';
import dynamic from 'next/dynamic';

const MyCertificates = () => {
const CertifcateUploadBox = dynamic(() => import('@/components/CertificateUploadBox'), {
ssr: false,
});

export default function MyCertificates() {
const router = useRouter();
const { orgId } = router.query;
return (
<DashboardLayout pageTitle="My Certificates" previousPage={`${orgId}`}>
<ComingSoon />
<DashboardLayout pageTitle="My Certificates" previousPage={`/${orgId}`}>
{/* <ComingSoon /> */}
<CertifcateUploadBox />
</DashboardLayout>
);
};
}

export default MyCertificates;
// export default MyCertificates;
3 changes: 1 addition & 2 deletions packages/tsconfig/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@
"esModuleInterop": true,
/* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true,
/* Ensure that casing is correct in imports. */ /* Type Checking */
"strict": true,
/* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true,
/* Enable all strict type-checking options. */ "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
Expand Down
Loading

0 comments on commit 9c3f32e

Please sign in to comment.