diff --git a/package-lock.json b/package-lock.json index 370d418..988f40e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-nice-avatar": "^1.5.0", + "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "sass": "^1.77.1", "typescript": "^4.9.5", @@ -3799,6 +3800,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -15409,6 +15418,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", + "dependencies": { + "@remix-run/router": "1.16.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", + "dependencies": { + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index 4bd90aa..22086e8 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-nice-avatar": "^1.5.0", + "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "sass": "^1.77.1", "typescript": "^4.9.5", diff --git a/src/App/App.tsx b/src/App/App.tsx index 3217d28..c796e5b 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -1,17 +1,22 @@ import React, { Component } from "react"; -import AvatarList from "./components/AvatarList/index"; -import AvatarEditor from "./components/AvatarEditor/index"; -import Footer from "./components/Footer"; +import { Routes, Route } from "react-router-dom"; import domtoimage from "dom-to-image"; import { saveAs } from "file-saver"; import ReactNiceAvatar, { genConfig } from "./config/index"; +import AvatarList from "./components/AvatarList"; +import AvatarEditor from "./components/AvatarEditor"; +import Footer from "./components/Footer"; import Header from "./components/Header"; +import Form from "./components/Form"; +import Arrow from "./components/Arrow"; +import AboutUs from "./components/AboutUs"; import "./index.scss"; interface AppState { config: { [key: string]: any }; shape: AvatarShape; avatarId: string; + avatarImageDataUrl: string | null; } type AvatarShape = "circle" | "rounded" | "square"; @@ -20,34 +25,43 @@ class App extends Component<{}, AppState> { constructor(props: {}) { super(props); this.state = { - config: genConfig({ - isGradient: Boolean(Math.round(Math.random())), - }), + config: genConfig({ isGradient: Boolean(Math.round(Math.random())) }), shape: "circle", - avatarId: "myAvatar", // Declare avatarId here + avatarId: "myAvatar", + avatarImageDataUrl: null, }; } - selectConfig(config: { [key: string]: any }) { + // Select configuration for avatar + selectConfig = (config: { [key: string]: any }) => { this.setState({ config }); - } + }; - updateConfig(key: string, value: any) { - // Specify type for value - const { config } = this.state; - config[key] = value; - this.setState({ config }); - } + // Update specific configuration key + updateConfig = (key: string, value: any) => { + this.setState((prevState) => ({ + config: { ...prevState.config, [key]: value }, + })); + }; - updateShape(shape: AvatarShape) { - // Specify type for shape + // Update the shape of the avatar + updateShape = (shape: AvatarShape) => { this.setState({ shape }); - } + }; - async download() { + // Download avatar as image + download = async () => { const scale = 2; const node = document.getElementById(this.state.avatarId); - if (node) { + + if (!node) { + console.error( + `Element with ID ${this.state.avatarId} not found in the DOM.` + ); + return; + } + + try { const blob = await domtoimage.toBlob(node, { height: node.offsetHeight * scale, style: { @@ -59,48 +73,101 @@ class App extends Component<{}, AppState> { width: node.offsetWidth * scale, }); - saveAs(blob, "avatar.png"); + if (blob) saveAs(blob, "avatar.png"); + else console.error("Blob is null or undefined."); + } catch (error) { + console.error("Error generating image blob:", error); } - } + }; - onInputKeyUp(e: React.KeyboardEvent) { - this.setState({ - config: genConfig(e.currentTarget.value), - }); - } + // Handle input key up event to generate config + onInputKeyUp = (e: React.KeyboardEvent) => { + this.setState({ config: genConfig(e.currentTarget.value) }); + }; + + // Capture avatar image as a data URL + captureAvatarImage = async () => { + const node = document.getElementById(this.state.avatarId); + if (node) { + try { + const dataUrl = await domtoimage.toPng(node); + this.setState({ avatarImageDataUrl: dataUrl }); + console.log("State updated:", dataUrl); + } catch (error) { + console.error("Error capturing avatar image:", error); + } + } else { + console.log("Node does not exist"); + } + }; + + // Handle form submission + handleFormSubmit = ( + formData: { name: string; region: string; role: string }, + imageDataUrl: string + ) => { + console.log("Form submitted", formData, imageDataUrl); + }; render() { - const { config, shape } = this.state; + const { config, shape, avatarImageDataUrl, avatarId } = this.state; + return ( -
-
+
+
-
- + } /> + +
+ +
+ + + + +
+ +
+ + } + /> + {}} + setSelectedRole={() => {}} + avatarImageDataUrl={avatarImageDataUrl} + /> + } /> -
- - ) => - this.onInputKeyUp(e) - } - /> +
- - -
); diff --git a/src/App/components/AboutUs.tsx b/src/App/components/AboutUs.tsx new file mode 100644 index 0000000..3afe8a5 --- /dev/null +++ b/src/App/components/AboutUs.tsx @@ -0,0 +1,35 @@ +import React from "react"; + +const AboutUs: React.FC = () => { + return ( +
+
+

About Us

+

+ Welcome to our avatar generation website! This project was initiated as + part of our learning journey in software development at CodeYourFuture. +

+

+ The primary objective of this website is to provide a platform for + individuals who cannot use their original pictures to generate avatars. + Throughout this project, We have been utilizing TypeScript to ensure + cleaner and more understandable code, facilitating easier maintenance + and scalability. +

+

+ Initially, we worked on refactoring the existing codebase, which was + written in legacy code. This process allowed us to gain in-depth + understanding of the project, fix bugs, and apply necessary updates. + Now, we are actively working on building new features to enhance the + website and achieve its objectives. +

+

+ This experience has been invaluable in teaching us how to approach new + projects written by others, identify and resolve issues effectively, and + seamlessly integrate new features into existing codebases. +

+
+ ); +}; + +export default AboutUs; diff --git a/src/App/components/Arrow.tsx b/src/App/components/Arrow.tsx new file mode 100644 index 0000000..3383f4c --- /dev/null +++ b/src/App/components/Arrow.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useNavigate } from "react-router-dom"; + +type ArrowProps = { + width?: number; + height?: number; + fillColor?: string; + onCaptureAvatar: () => void; // Add a new prop for capturing avatar image +}; + +const Arrow = ({ + width = 100, + height = 50, + fillColor = "black", + onCaptureAvatar, +}: ArrowProps) => { + const navigate = useNavigate(); + + const handleClick = async (event: React.MouseEvent) => { + event.preventDefault(); + await onCaptureAvatar(); // Capture the avatar image + navigate("/form"); + }; + + return ( + + + + + + ); +}; + +export default Arrow; diff --git a/src/App/components/Form.tsx b/src/App/components/Form.tsx index 6c80b1a..23d0846 100644 --- a/src/App/components/Form.tsx +++ b/src/App/components/Form.tsx @@ -3,6 +3,9 @@ import { TextField, Button, Grid, Card } from "@mui/material"; interface FormProps { onSubmit: (formData: FormData, imageDataUrl: string) => void; + avatarImageDataUrl: string | null; + setSelectedRegion: React.Dispatch>; + setSelectedRole: React.Dispatch>; } interface FormData { @@ -11,13 +14,17 @@ interface FormData { role: string; } -const Form: React.FC = ({ onSubmit }) => { +const Form: React.FC = ({ + onSubmit, + avatarImageDataUrl, + setSelectedRegion, + setSelectedRole, +}) => { const [formData, setFormData] = useState({ name: "", region: "", role: "", }); - const [imageDataUrl, setImageDataUrl] = useState(null); const handleChange = (e: React.ChangeEvent) => { @@ -27,32 +34,46 @@ const Form: React.FC = ({ onSubmit }) => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + console.log("clicked"); + console.log(avatarImageDataUrl); + if (avatarImageDataUrl) { + fetch(avatarImageDataUrl) + .then((response) => response.blob()) + .then((blob) => { + const dataURL = URL.createObjectURL(blob); + setImageDataUrl(dataURL); + + // Display the image in the
with the id "picture" + const pictureDiv = document.getElementById("picture"); + if (pictureDiv) { + // Clear previous content in the pictureDiv + pictureDiv.innerHTML = ""; + // Create an img element + const img = document.createElement("img"); + // Set the src attribute to the blob URL + img.src = dataURL; + // Append the img element to the pictureDiv + pictureDiv.appendChild(img); + } - // Generate image - const canvas = document.createElement("canvas"); - canvas.width = 400; - canvas.height = 200; - const ctx = canvas.getContext("2d"); - if (ctx) { - ctx.fillStyle = "#ffffff"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = "#000000"; - ctx.font = "20px Arial"; - ctx.fillText(`Name: ${formData.name}`, 10, 30); - ctx.fillText(`Region: ${formData.region}`, 10, 60); - ctx.fillText(`Role: ${formData.role}`, 10, 90); - const dataUrl = canvas.toDataURL("image/png"); - setImageDataUrl(dataUrl); - onSubmit(formData, dataUrl); // Call the onSubmit callback with form data and image data URL + onSubmit(formData, dataURL); + }); + } else { + console.error("No avatar image URL provided."); } }; return ( -
- - + + + @@ -100,9 +121,16 @@ const Form: React.FC = ({ onSubmit }) => { - + + + + +
+ {/* {imageDataUrl && Avatar} */} +
+
- +
); }; diff --git a/src/App/components/Header.tsx b/src/App/components/Header.tsx index 44b4bdf..d0fe21a 100644 --- a/src/App/components/Header.tsx +++ b/src/App/components/Header.tsx @@ -1,34 +1,35 @@ import React from "react"; -import { - AppBar, - Toolbar, - Typography, - Button, - Link as MuiLink, -} from "@mui/material"; +import { Link, useNavigate } from "react-router-dom"; interface HeaderProps { title: string; } const Header: React.FC = ({ title }) => { + const navigate = useNavigate(); + + const handleHeaderClick = () => { + navigate("/"); + }; + return ( - - - - {title} - - - - - + + +
); }; diff --git a/src/App/components/ImageGenerator.tsx b/src/App/components/ImageGenerator.tsx index c6e29b7..17be7f8 100644 --- a/src/App/components/ImageGenerator.tsx +++ b/src/App/components/ImageGenerator.tsx @@ -2,11 +2,15 @@ import React from "react"; import { Button, Card, CardActions, CardContent } from "@mui/material"; interface ImageGeneratorProps { - generatedImageDataUrl: string | null; // Change prop name to generatedImageDataUrl + generatedImageDataUrl: string; + selectedRegion: string; + selectedRole: string; } const ImageGenerator: React.FC = ({ generatedImageDataUrl, + selectedRegion, + selectedRole, }) => { // Use generatedImageDataUrl prop const handleDownload = () => { @@ -25,9 +29,21 @@ const ImageGenerator: React.FC = ({ <> Generated +

+ {selectedRegion} +

diff --git a/src/App/index.scss b/src/App/index.scss index 9dbd0e7..4115491 100644 --- a/src/App/index.scss +++ b/src/App/index.scss @@ -1,6 +1,6 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; .App { min-height: 100vh; @@ -11,3 +11,19 @@ .inputField { background: rgba(255, 255, 255, 0.1); } + +@keyframes moveRight { + 0% { + transform: translateX(0px); + } + 50% { + transform: translateX(-20px); + } + 100% { + transform: translateX(30px); + } +} + +.animate-right { + animation: moveRight 2s infinite; +} diff --git a/src/AvatarForm.tsx b/src/AvatarForm.tsx new file mode 100644 index 0000000..7a4ce7d --- /dev/null +++ b/src/AvatarForm.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import { Grid } from "@mui/material"; +import ImageGenerator from "./App/components/ImageGenerator"; +import Header from "./App/components/Header"; +import Form from "./App/components/Form"; +import Footer from "./App/components/Footer"; + +const App: React.FC = () => { + const [formData, setFormData] = useState(null); + const [selectedRole, setSelectedRole] = useState(""); + const [selectedRegion, setSelectedRegion] = useState(""); + const [generatedImageDataUrl, setGeneratedImageDataUrl] = useState< + string | null + >(null); + + const handleSubmit = (data: any, imageDataUrl: string) => { + setFormData(data); + setGeneratedImageDataUrl(imageDataUrl); + }; + + return ( +
+
+
+ + +
+ + + {generatedImageDataUrl && ( + + )} + + +
+
+
+ ); +}; + +export default App; diff --git a/src/index.scss b/src/index.scss index c41e8fe..3d9bd37 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,5 +1,4 @@ @import "../src/scss/iconfont"; - @import "../src/scss/_fonts.scss"; html { diff --git a/src/index.tsx b/src/index.tsx index 6eb01ab..7853277 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { BrowserRouter as Router } from "react-router-dom"; import ReactDOM from "react-dom/client"; import "./index.scss"; import App from "./App/App"; @@ -8,12 +9,9 @@ const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement ); root.render( - + - + ); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); diff --git a/tsconfig.json b/tsconfig.json index 17cb279..edd0aa0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["react", "react-dom", "react-router-dom"] }, "include": ["src", "tailwind.config.js"] }