diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..c752985 Binary files /dev/null and b/.DS_Store differ diff --git a/packages/.DS_Store b/packages/.DS_Store new file mode 100644 index 0000000..0bf0f67 Binary files /dev/null and b/packages/.DS_Store differ diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore index a547bf3..288bdc5 100644 --- a/packages/frontend/.gitignore +++ b/packages/frontend/.gitignore @@ -17,8 +17,10 @@ dist-ssr !.vscode/extensions.json .idea .DS_Store +**/.DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? + diff --git a/packages/frontend/package-lock.json b/packages/frontend/package-lock.json index d54b5d9..43fd993 100644 --- a/packages/frontend/package-lock.json +++ b/packages/frontend/package-lock.json @@ -8,8 +8,10 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "clsx": "^2.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-icons": "^5.3.0", "react-router-dom": "^6.27.0", "zustand": "^5.0.0" }, @@ -1682,6 +1684,14 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3011,6 +3021,14 @@ "react": "^18.3.1" } }, + "node_modules/react-icons": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-router": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 552fef2..47f69b4 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "clsx": "^2.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-icons": "^5.3.0", "react-router-dom": "^6.27.0", "zustand": "^5.0.0" }, diff --git a/packages/frontend/src/lib/Button.tsx b/packages/frontend/src/lib/Button.tsx new file mode 100644 index 0000000..d798717 --- /dev/null +++ b/packages/frontend/src/lib/Button.tsx @@ -0,0 +1,26 @@ +import clsx from "clsx"; + +type ButtonProps = { + isDisabled?: boolean; + className?: string; +}; + +function Button({ isDisabled, className }: ButtonProps) { + return ( + + Submit + + ); +} + +export default Button; diff --git a/packages/frontend/src/lib/Input.tsx b/packages/frontend/src/lib/Input.tsx new file mode 100644 index 0000000..8470c36 --- /dev/null +++ b/packages/frontend/src/lib/Input.tsx @@ -0,0 +1,149 @@ +import clsx from "clsx"; +import React, { useEffect, useState } from "react"; + +interface InputProps { + type?: "text" | "email" | "textarea" | "password"; + value: string; + onChange?: (value: string) => void; + label: string; + readOnly?: boolean; + name: string; + isRequired: boolean; + minLength?: number; + maxLength?: number; + bottomText?: string; + pattern?: string; + className?: string; + disabled?: boolean; + [key: string]: any; +} + +interface IsRequiredComponentProps { + isRequired: boolean; +} + +const Input: React.FC = ({ + type = "text", + value: propValue = "", + onChange, + label = "Name", + readOnly, + isRequired, + bottomText, + pattern, + minLength, + maxLength, + error, + disabled, + className, + name, + ...props +}) => { + const [inputValue, setInputValue] = useState(propValue); + const [showPassword, setShowPassword] = useState(false); + + useEffect(() => { + setInputValue(propValue); + }, [propValue]); + + const handleChange = ( + e: + | React.ChangeEvent + | React.ChangeEvent + ) => { + const newValue = e.target.value; + setInputValue(newValue); + if (onChange) { + onChange(newValue); + } + }; + + const togglePasswordVisibility = () => { + setShowPassword((prev) => !prev); + }; + + if (type === "textarea") { + return ( + + + {label} + + + + + ); + } + + return ( + + + {label} + + + + + + {type === "password" && ( + + {showPassword ? "🙈" : "👁️"}{" "} + + )} + + + ); +}; + +function IsRequiredField({ isRequired }: IsRequiredComponentProps) { + return ( + + {isRequired ? "*" : "(Optional)"} + + ); +} + +export default Input; diff --git a/packages/frontend/src/lib/icons/MetamaskIcon.tsx b/packages/frontend/src/lib/icons/MetamaskIcon.tsx new file mode 100644 index 0000000..cdfe3b5 --- /dev/null +++ b/packages/frontend/src/lib/icons/MetamaskIcon.tsx @@ -0,0 +1,147 @@ +import clsx from "clsx"; +import React from "react"; + +interface MetaMaskIconProps { + size?: number; + className?: string; +} + +const MetaMaskIcon: React.FC = ({ + size = "1em", + className, +}) => { + return ( + + MetaMask + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default MetaMaskIcon; diff --git a/packages/frontend/src/routes/Routes.tsx b/packages/frontend/src/routes/Routes.tsx index 2b80201..eb78011 100644 --- a/packages/frontend/src/routes/Routes.tsx +++ b/packages/frontend/src/routes/Routes.tsx @@ -3,12 +3,14 @@ import Home from "./home"; import Login from "./login"; import DashboardHome from "./dashboard/home"; import Projects from "./dashboard/projects"; +import SignUp from "./signup"; export default function Routes() { return ( } /> } /> + } /> } /> } /> diff --git a/packages/frontend/src/routes/login/index.tsx b/packages/frontend/src/routes/login/index.tsx index 846a073..8efc333 100644 --- a/packages/frontend/src/routes/login/index.tsx +++ b/packages/frontend/src/routes/login/index.tsx @@ -1,7 +1,142 @@ -import React from "react"; +import { FormEvent, useState } from "react"; +import Navbar from "../../lib/navbar"; +import Input from "../../lib/Input"; +import { emailRegex } from "../../utils/utils"; +import Button from "../../lib/Button"; +import { FaGoogle, FaGithub } from "react-icons/fa"; +import MetaMaskIcon from "../../lib/icons/MetamaskIcon"; + +export type FormValuesType = { + email: string; + password: string; +}; +const defaultFormValues = { + email: "", + password: "", +}; function Login() { - return index; + const [formValues, setFormValues] = useState({ + ...defaultFormValues, + }); + + const [isButtonDisabled, setIsButtonDisabled] = useState(true); + + const handleFormSubmit = async (event: FormEvent) => { + event.preventDefault(); + + // await submitForm(formValues, resetFormValues); + }; + + const handleInputChange = (name: string, value: string) => { + setFormValues((prevValues) => ({ + ...prevValues, + [name]: value, + })); + + if (name === "comment") { + const shouldBeDisabled = value.trim().length < 30; + + if (shouldBeDisabled !== isButtonDisabled) { + setIsButtonDisabled(shouldBeDisabled); + } + } + }; + + const handleGoogleSignIn = () => { + // Implement Google sign-in logic here + console.log("Google sign-in clicked"); + }; + + const handleGitHubSignIn = () => { + // Implement GitHub sign-in logic here + console.log("GitHub sign-in clicked"); + }; + + const handleMetaMaskSignIn = () => { + // Implement MetaMask sign-in logic here + console.log("MetaMask sign-in clicked"); + }; + + return ( + + + + + + + handleInputChange("email", value)} + label="Email address" + /> + + + handleInputChange("password", value)} + label="Password" + /> + + + + + + or + + + + + + Sign in with Google + + + + Sign in with GitHub + + + + Sign in with MetaMask + + + + + + Don't have an account?{" "} + + Sign Up + + + + + + + + ); } export default Login; diff --git a/packages/frontend/src/routes/signup/index.tsx b/packages/frontend/src/routes/signup/index.tsx new file mode 100644 index 0000000..5616997 --- /dev/null +++ b/packages/frontend/src/routes/signup/index.tsx @@ -0,0 +1,142 @@ +import { FormEvent, useState } from "react"; +import Navbar from "../../lib/navbar"; +import Input from "../../lib/Input"; +import { emailRegex } from "../../utils/utils"; +import Button from "../../lib/Button"; +import { FaGoogle, FaGithub } from "react-icons/fa"; +import MetaMaskIcon from "../../lib/icons/MetamaskIcon"; + +export type FormValuesType = { + email: string; + password: string; +}; +const defaultFormValues = { + email: "", + password: "", +}; + +function SignUp() { + const [formValues, setFormValues] = useState({ + ...defaultFormValues, + }); + + const [isButtonDisabled, setIsButtonDisabled] = useState(true); + + const handleFormSubmit = async (event: FormEvent) => { + event.preventDefault(); + + // await submitForm(formValues, resetFormValues); + }; + + const handleInputChange = (name: string, value: string) => { + setFormValues((prevValues) => ({ + ...prevValues, + [name]: value, + })); + + if (name === "comment") { + const shouldBeDisabled = value.trim().length < 30; + + if (shouldBeDisabled !== isButtonDisabled) { + setIsButtonDisabled(shouldBeDisabled); + } + } + }; + + const handleGoogleSignIn = () => { + // Implement Google sign-in logic here + console.log("Google sign-in clicked"); + }; + + const handleGitHubSignIn = () => { + // Implement GitHub sign-in logic here + console.log("GitHub sign-in clicked"); + }; + + const handleMetaMaskSignIn = () => { + // Implement MetaMask sign-in logic here + console.log("MetaMask sign-in clicked"); + }; + + return ( + + + + + + + + + Sign up with Google + + + + Sign up with GitHub + + + + Sign up with MetaMask + + + + + or + + + + handleInputChange("email", value)} + label="Email address" + /> + + + handleInputChange("password", value)} + label="Password" + /> + + + + + + + Already have an account?{" "} + + Log In + + + + + + + + ); +} + +export default SignUp; diff --git a/packages/frontend/src/utils/utils.ts b/packages/frontend/src/utils/utils.ts new file mode 100644 index 0000000..763ae08 --- /dev/null +++ b/packages/frontend/src/utils/utils.ts @@ -0,0 +1,2 @@ +export const emailRegex = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/; diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 2157c2e..c8f5ae6 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -1,4 +1,22 @@ /** @type {import('tailwindcss').Config} */ + +export const colors = { + "ui-mid-white": "#313538", + "yellow-dark-9": "#FFE629", + "yellow-dark-1": "#14120B", + "yellow-dark-2": "#1B180F", + "yellow-dark-3": "#2D2305", + "yellow-dark-4": "#362B00", + "yellow-dark-5": "#433500", + "yellow-dark-6": "#524202", + "yellow-dark-7": "#665417", + "yellow-dark-8": "#836A21", + "yellow-dark-9": "#FFE629", + "yellow-dark-10": "#FFFF57", + "yellow-dark-11": "#F5E147", + "yellow-dark-12": "#F6EEB4", +}; + export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { @@ -9,6 +27,7 @@ export default { "100%": { transform: "translateY(0)", opacity: 1 }, }, }, + colors, animation: { slideUp: "slideUp 0.5s ease-out", },