improved website
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import InputBox from './InputBox';
|
||||
|
||||
export default function ContactForm() {
|
||||
const [ error, setError ] = useState<string>("");
|
||||
const [ success, setSuccess ] = useState<boolean>(false);
|
||||
const [ inputState, setInputState ] = useState<{name: string, email: string, subject: string, message: string}>({
|
||||
name: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: ""
|
||||
});
|
||||
|
||||
const onSubmit = () => {
|
||||
if (
|
||||
inputState.name === ""
|
||||
|| inputState.email === ""
|
||||
|| inputState.subject === ""
|
||||
|| inputState.message === ""
|
||||
) {
|
||||
setError("A required field was missing")
|
||||
} else {
|
||||
console.log("Form submitted")
|
||||
|
||||
fetch("https://api.alexav.gg/v4/contact", {
|
||||
method: "post",
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(inputState)
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
setSuccess(true)
|
||||
} else {
|
||||
setError("An error occurred while submitting your info.")
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error(e)
|
||||
setError("An error occurred while submitting your info.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setError("")
|
||||
}, 6000)
|
||||
}, [error]);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setSuccess(false)
|
||||
}, 6000)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg text-black p-2">
|
||||
<h1 className="text-2xl font-bold">Contact Me</h1>
|
||||
<hr className="my-2 border-black" />
|
||||
|
||||
<div className="space-y-2">
|
||||
<InputBox
|
||||
isText={true}
|
||||
name="name"
|
||||
placeholder="Name.."
|
||||
onChange={(value: string) => {
|
||||
setInputState((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
name: value
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<InputBox
|
||||
name="email"
|
||||
isText={true}
|
||||
placeholder="Email.."
|
||||
onChange={(value: string) => {
|
||||
setInputState((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
email: value
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<InputBox
|
||||
isText={true}
|
||||
name="subject"
|
||||
placeholder="Subject.."
|
||||
onChange={(value: string) => {
|
||||
setInputState((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
subject: value
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<InputBox
|
||||
isText={false}
|
||||
name="message"
|
||||
placeholder="Message.."
|
||||
onChange={(value: string) => {
|
||||
setInputState((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
message: value
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="block">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-black w-full text-white font-bold rounded-lg p-2"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<p className="text-red-400 font-bold text-center">{error}</p>
|
||||
<p className="font-bold text-center">{ success && "Success! Your message has been received"}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -15,8 +15,9 @@ export default function Footer() {
|
||||
}, [mailState])
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-600 text-center text-white absolute bottom-0 left-0 right-0 p-4 select-none">
|
||||
<div className="bg-zinc-600 text-center text-white bottom-0 left-0 right-0 p-4 select-none">
|
||||
<h1 className="font-bold">© {new Date().getFullYear()}, Alex Frantz. All Rights Reserved</h1>
|
||||
<p>All logos & images on this page belong to their respective owners.</p>
|
||||
|
||||
{
|
||||
mailState ? (
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
export default function Header() {
|
||||
return (
|
||||
<div className="text-white pt-4">
|
||||
<div className="text-white pt-4 mx-auto lg:w-1/2 sm:w-full">
|
||||
<h1 className="font-bold text-2xl">Alex Frantz</h1>
|
||||
<p>Professional button clicker & word typer</p>
|
||||
<div className="inline-block space-x-2">
|
||||
<a className="font-bold hover:underline" href="/">Home</a>
|
||||
<a className="font-bold hover:underline" href="experience">Experience</a>
|
||||
</div>
|
||||
|
||||
<hr className="my-4" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export default function InputBox({
|
||||
name,
|
||||
placeholder,
|
||||
isText = true,
|
||||
onChange
|
||||
}: {
|
||||
name: string,
|
||||
isText?: boolean,
|
||||
placeholder: string,
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
if (isText) {
|
||||
return (
|
||||
<div className="block">
|
||||
<input
|
||||
type="text"
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
className="bg-black rounded-lg p-2 text-white w-full"
|
||||
onChange={({target}) => onChange(target.value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="block">
|
||||
<textarea
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
onChange={({ target }) => onChange(target.value)}
|
||||
className="bg-black rounded-lg p-2 text-white w-full h-32"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default function ListBox({
|
||||
title,
|
||||
points
|
||||
}: {
|
||||
title: string,
|
||||
points: string[]
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-slate-100 text-black rounded-lg p-4">
|
||||
<h3 className="text-lg font-bold">{title}</h3>
|
||||
<ul className="mt-2 space-y-2 text-muted-foreground">
|
||||
{
|
||||
points.map((v,k) => {
|
||||
return <li key={k}>{v}</li>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+19
-38
@@ -1,51 +1,32 @@
|
||||
export default function Project({
|
||||
name,
|
||||
image,
|
||||
description,
|
||||
urlTo,
|
||||
subtitle,
|
||||
bullets
|
||||
urlTo
|
||||
}: {
|
||||
name: string,
|
||||
image: string,
|
||||
description: string,
|
||||
urlTo?: string,
|
||||
subtitle?: string,
|
||||
bullets?: string[]
|
||||
}) {
|
||||
const mapBullets = () => {
|
||||
return bullets?.map((v,k) => {
|
||||
return <li className="list-inside list-disc" key={k}>{v}</li>
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white text-black p-2">
|
||||
<div className="float-left">
|
||||
<p className="pl-2 text-2xl font-bold">{name}
|
||||
{
|
||||
urlTo ? (
|
||||
<a className="text-sm hover:underline pl-2" target="_blank" href={urlTo}>
|
||||
Project Website
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-base pl-2">
|
||||
{subtitle}
|
||||
</span>
|
||||
)
|
||||
<a href={urlTo} target="_blank">
|
||||
<div className="bg-white text-black max-w-fit mx-auto rounded-lg overflow-hidden">
|
||||
<img
|
||||
src={image}
|
||||
width="400"
|
||||
height="300"
|
||||
alt={name}
|
||||
className="w-full h-48 object-cover"
|
||||
/>
|
||||
|
||||
}
|
||||
</p>
|
||||
<div className="p-4 space-y-2">
|
||||
<h3 className="text-xl font-bold">{name}</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="clear-left text-left pl-2.5">
|
||||
<p>{description}</p>
|
||||
{
|
||||
bullets && (
|
||||
<ul>
|
||||
{mapBullets()}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user