diff --git a/package-lock.json b/package-lock.json
index 370d418..8e58e38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,8 +23,10 @@
"dom-to-image": "^2.6.0",
"file-saver": "^2.0.5",
"react": "^18.2.0",
+ "react-curved-text": "^3.0.1",
"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",
@@ -34,6 +36,7 @@
"@types/chroma-js": "^2.4.4",
"@types/dom-to-image": "^2.6.7",
"@types/file-saver": "^2.0.7",
+ "@types/react-curved-text": "^2.0.3",
"tailwindcss": "^3.4.3"
}
},
@@ -3799,6 +3802,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",
@@ -4688,6 +4699,15 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-curved-text": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-curved-text/-/react-curved-text-2.0.3.tgz",
+ "integrity": "sha512-J7yy3DJX+I+jQWk5r8IZaVyOLi0bsirD7k0yq2X6IwYTNSCVXfiEYYYq0Yoy4ZErdC2YV2nku+OQIxapPfG/Tg==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-dom": {
"version": "18.2.25",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz",
@@ -7457,6 +7477,12 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
+ "node_modules/dommatrix": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
+ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
+ "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
+ },
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -15249,6 +15275,18 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "node_modules/react-curved-text": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/react-curved-text/-/react-curved-text-3.0.1.tgz",
+ "integrity": "sha512-tHRDAfiVa8bVMTadRdHfGoO/PjPgLF42RODAWoQlc2PNnwTDBh0hbSAZtNiNSmd8C3gB2d0rlNd841UXb0/rTQ==",
+ "dependencies": {
+ "svg-path-commander": "1.0.5"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@@ -15409,6 +15447,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",
@@ -16986,6 +17054,14 @@
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
},
+ "node_modules/svg-path-commander": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/svg-path-commander/-/svg-path-commander-1.0.5.tgz",
+ "integrity": "sha512-hhQfARVXoPrwwe4DNPWM4hjQLK7rTxwQ+TUc5mxoe5g0k5eStc4SPnKqmivlm/dzZ98bI+yDyKye92n2t+oOiQ==",
+ "dependencies": {
+ "dommatrix": "^1.0.3"
+ }
+ },
"node_modules/svgo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",
diff --git a/package.json b/package.json
index 4bd90aa..f2be072 100644
--- a/package.json
+++ b/package.json
@@ -18,8 +18,10 @@
"dom-to-image": "^2.6.0",
"file-saver": "^2.0.5",
"react": "^18.2.0",
+ "react-curved-text": "^3.0.1",
"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",
@@ -53,6 +55,7 @@
"@types/chroma-js": "^2.4.4",
"@types/dom-to-image": "^2.6.7",
"@types/file-saver": "^2.0.7",
+ "@types/react-curved-text": "^2.0.3",
"tailwindcss": "^3.4.3"
}
}
diff --git a/src/App/App.tsx b/src/App/App.tsx
index 3217d28..4fcbd31 100644
--- a/src/App/App.tsx
+++ b/src/App/App.tsx
@@ -1,4 +1,5 @@
import React, { Component } from "react";
+import { Routes, Route } from "react-router-dom";
import AvatarList from "./components/AvatarList/index";
import AvatarEditor from "./components/AvatarEditor/index";
import Footer from "./components/Footer";
@@ -7,11 +8,15 @@ import { saveAs } from "file-saver";
import ReactNiceAvatar, { genConfig } from "./config/index";
import Header from "./components/Header";
import "./index.scss";
+import Form from "./components/Form";
+import Arrow from "./components/Arrow";
+import AboutUs from "./components/AboutUs";
interface AppState {
config: { [key: string]: any };
shape: AvatarShape;
avatarId: string;
+ avatarImageDataUrl: string | null;
}
type AvatarShape = "circle" | "rounded" | "square";
@@ -24,8 +29,11 @@ class App extends Component<{}, AppState> {
isGradient: Boolean(Math.round(Math.random())),
}),
shape: "circle",
- avatarId: "myAvatar", // Declare avatarId here
+ avatarId: "myAvatar",
+ avatarImageDataUrl: null,
};
+ this.handleFormSubmit = this.handleFormSubmit.bind(this);
+ this.captureAvatarImage = this.captureAvatarImage.bind(this);
}
selectConfig(config: { [key: string]: any }) {
@@ -33,21 +41,29 @@ class App extends Component<{}, AppState> {
}
updateConfig(key: string, value: any) {
- // Specify type for value
const { config } = this.state;
config[key] = value;
this.setState({ config });
}
updateShape(shape: AvatarShape) {
- // Specify type for shape
this.setState({ shape });
}
async download() {
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,7 +75,13 @@ 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);
}
}
@@ -69,38 +91,84 @@ class App extends Component<{}, AppState> {
});
}
+ async captureAvatarImage() {
+ const node = document.getElementById(this.state.avatarId);
+ if (node) {
+ const dataUrl = await domtoimage.toPng(node);
+ console.log(dataUrl);
+ this.setState({ avatarImageDataUrl: dataUrl }, () => {
+ console.log("State updated:", this.state.avatarImageDataUrl);
+ });
+ } else console.log("node is not exist");
+ }
+
+ 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 } = this.state;
return (
-
-
+
+
-
-
- ) =>
- this.onInputKeyUp(e)
- }
- />
+ {}}
+ setSelectedRole={() => {}}
+ avatarImageDataUrl={avatarImageDataUrl}
+ />
+ }
+ />
+
-
-
-
);
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..cdaa2fc 100644
--- a/src/App/components/Form.tsx
+++ b/src/App/components/Form.tsx
@@ -1,8 +1,22 @@
-import React, { useState } from "react";
-import { TextField, Button, Grid, Card } from "@mui/material";
+import ReactCurvedText from 'react-curved-text';
+import React, { useState, useEffect } from "react";
+import {
+ Button,
+ Grid,
+ Card,
+ Select,
+ MenuItem,
+ InputLabel,
+ FormControl,
+ SelectChangeEvent,
+} from "@mui/material";
+import ReactDOM from 'react-dom';
interface FormProps {
onSubmit: (formData: FormData, imageDataUrl: string) => void;
+ avatarImageDataUrl: string | null;
+ setSelectedRegion: React.Dispatch>;
+ setSelectedRole: React.Dispatch>;
}
interface FormData {
@@ -11,82 +25,195 @@ 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) => {
+ const pictureByClickingArrow = () => {
+ if (avatarImageDataUrl) {
+ fetch(avatarImageDataUrl)
+ .then((response) => response.blob())
+ .then((blob) => {
+ const dataURL = URL.createObjectURL(blob);
+ setImageDataUrl(dataURL);
+ const pictureDiv = document.getElementById("picture");
+ if (pictureDiv) {
+ pictureDiv.innerHTML = "";
+ const img = document.createElement("img");
+ img.src = dataURL;
+ img.style.position = "relative";
+ pictureDiv.appendChild(img);
+ }
+ onSubmit(formData, dataURL);
+ });
+ } else {
+ console.error("No avatar image URL provided.");
+ }
+ };
+
+ useEffect(() => {
+ pictureByClickingArrow();
+ }, []); // Empty dependency array ensures this runs only once when the component mounts
+
+ const handleChange = (
+ e: React.ChangeEvent | SelectChangeEvent
+ ) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
+ if (avatarImageDataUrl) {
+ fetch(avatarImageDataUrl)
+ .then((response) => response.blob())
+ .then((blob) => {
+ const dataURL = URL.createObjectURL(blob);
+ setImageDataUrl(dataURL);
+
+ const pictureDiv = document.getElementById("picture");
+ if (pictureDiv) {
+ pictureDiv.innerHTML = "";
+
+ // Role
+ const roleP = document.createElement("p");
+ roleP.innerText = formData.role;
+ roleP.style.position = "absolute";
+ roleP.style.top = "1px";
+ roleP.style.left = "0px";
+ roleP.style.right = "0px";
+ roleP.style.color = "black";
+ roleP.style.backgroundColor = "transparent";
+
+ // Image
+ const img = document.createElement("img");
+ img.src = dataURL;
+ img.style.position = "relative";
+ img.style.border = "40px solid red";
+ img.style.borderRadius = "160px";
+ img.style.width = "256px";
+ img.style.height = "256px";
+
+ // Create a wrapper div for ReactCurvedText
+ const curvedTextDiv = document.createElement("div");
+ curvedTextDiv.id = "curvedTextDiv";
+ curvedTextDiv.style.position = "absolute";
+ curvedTextDiv.style.top = "0";
+ curvedTextDiv.style.left = "0";
+ curvedTextDiv.style.width = "100%";
+ curvedTextDiv.style.height = "100%";
+ curvedTextDiv.style.pointerEvents = "none";
+
+ pictureDiv.style.position = "relative";
+ pictureDiv.style.width = "256px"; //
+ pictureDiv.style.height = "256px";
+
+ pictureDiv.appendChild(img);
+ pictureDiv.appendChild(roleP);
+ pictureDiv.appendChild(curvedTextDiv);
+
+ // Render the ReactCurvedText component into the wrapper div
+ ReactDOM.render(
+ ,
+ curvedTextDiv
+ );
- // 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
+ //
+ ReactDOM.render(
+ ,
+ roleP
+ );
+ }
+
+ onSubmit(formData, dataURL);
+ });
+ } else {
+ console.error("No avatar image URL provided.");
}
};
+
return (
-
+
+
+
+
+
+
-
+
);
};
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}
-
-
+
+
+ {title}
+
+
+
About Us
-
-
+
+
Contact
-
-
-
+
+
+
);
};
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 = ({
<>
+
+ {selectedRegion}
+
Download Image
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"]
}