Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
175 changes: 121 additions & 54 deletions src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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: {
Expand All @@ -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<HTMLInputElement>) {
this.setState({
config: genConfig(e.currentTarget.value),
});
}
// Handle input key up event to generate config
onInputKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
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 (
<div className="App flex flex-col min-h-screen">
<Header title="Avatar Generator" />
<div className="App flex flex-col min-h-screen overflow-x-hidden">
<Header title="AVANART" />
<main className="flex-1 flex flex-col items-center justify-center">
<div id={this.state.avatarId} className="mb-10">
<ReactNiceAvatar
className="w-64 h-64 highres:w-80 highres:h-80"
hairColorRandom
shape={this.state.shape}
{...config}
<Routes>
<Route path="/about-us" element={<AboutUs />} />
<Route
path="/"
element={
<>
<div id={avatarId} className="mb-10">
<ReactNiceAvatar
className="w-64 h-64 highres:w-80 highres:h-80"
hairColorRandom
shape={shape}
{...config}
/>
</div>
<AvatarEditor
config={config}
shape={shape}
updateConfig={this.updateConfig}
updateShape={this.updateShape}
download={this.download}
/>
<input
className="inputField w-64 h-10 p-2 rounded-full mt-10 text-center outline-none"
placeholder="input name or email ..."
onKeyUp={this.onInputKeyUp}
/>
<AvatarList selectConfig={this.selectConfig} />
<button onClick={this.captureAvatarImage} className="mt-4">
Capture Avatar Image
</button>
<div className="absolute top-2/3 right-0">
<Arrow
fillColor="red"
onCaptureAvatar={this.captureAvatarImage}
/>
</div>
</>
}
/>
<Route
path="/form"
element={
<Form
onSubmit={this.handleFormSubmit}
setSelectedRegion={() => {}}
setSelectedRole={() => {}}
avatarImageDataUrl={avatarImageDataUrl}
/>
}
/>
</div>
<AvatarEditor
config={config}
shape={shape}
updateConfig={this.updateConfig.bind(this)}
updateShape={this.updateShape.bind(this)}
download={this.download.bind(this)}
/>
<input
className="inputField w-64 h-10 p-2 rounded-full mt-10 text-center outline-none"
placeholder="input name or email ..."
onKeyUp={(e: React.KeyboardEvent<HTMLInputElement>) =>
this.onInputKeyUp(e)
}
/>
</Routes>
</main>

<AvatarList selectConfig={this.selectConfig.bind(this)} />

<Footer />
</div>
);
Expand Down
35 changes: 35 additions & 0 deletions src/App/components/AboutUs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

const AboutUs: React.FC = () => {
return (
<div className="container mx-auto px-4 py-8 relative">
<div className="absolute inset-0 flex justify-center items-center"></div>
<h1 className="text-3xl text-pink-500 font-bold mb-4">About Us</h1>
<p className="text-lg mb-4">
Welcome to our avatar generation website! This project was initiated as
part of our learning journey in software development at CodeYourFuture.
</p>
<p className="text-md mb-2">
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.
</p>
<p className="text-md mb-2 ">
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.
</p>
<p className="text-md mb-2">
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.
</p>
</div>
);
};

export default AboutUs;
45 changes: 45 additions & 0 deletions src/App/components/Arrow.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLAnchorElement, MouseEvent>) => {
event.preventDefault();
await onCaptureAvatar(); // Capture the avatar image
navigate("/form");
};

return (
<a href="/form" onClick={handleClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 25 25"
width={width}
height={height}
fill={fillColor}
className="animate-right"
>
<path
style={{ fill: fillColor }}
d="m17.5 5.999-.707.707 5.293 5.293H1v1h21.086l-5.294 5.295.707.707L24 12.499l-6.5-6.5z"
data-name="Right"
/>
</svg>
</a>
);
};

export default Arrow;
Loading