Add contact form

This commit is contained in:
Nomi Nonsense (Nonszy) 2026-01-01 09:25:02 +07:00
parent 107ee8794d
commit 3866e8ade0
7 changed files with 165 additions and 9 deletions

View File

@ -0,0 +1,7 @@
import { NextResponse } from "next/server";
export function GET (req: Request) {
console.log(req.headers.get("x-forwarded-for") ?? 'damn');
return NextResponse.json({ status: "ok" });
}

View File

@ -22,7 +22,7 @@
animation-timing-function: linear(0.2, 0.8, 1);
}
50% {
transform: translate(0px, -180px) scale(1.18, 0.9);
transform: translate(0px, -120px) scale(1.18, 0.9);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
}

View File

@ -0,0 +1,96 @@
'use client'
import { ChangeEvent, useRef, useState } from "react";
import { CheckboxInput, Input, Submit } from "./form";
import { FloatingLabel } from "../floating-label";
export default function ContactForm() {
const [anon, setAnon] = useState(false);
const formRef = useRef<HTMLFormElement | null>(null);
const [status, setStatus] = useState<'success' | 'loading' | 'failed' | 'idle'>('idle');
const [errorMsg, setErrorMsg] = useState("");
const statusMsg = {
success: 'Sended!',
loading: 'Sending...',
failed: 'Failed :(',
idle: 'Send!'
}
const send = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setStatus('idle');
setErrorMsg('');
if (status == 'loading' || !formRef.current) return;
const formData = new FormData(formRef.current);
const data = {
anon,
name: formData.get('name')?.toString(),
email: formData.get('email')?.toString(),
message: formData.get('message')!.toString()
}
if ((data.name && data.email) && (data.name.length < 1 || data.email.length < 1) && !data.anon) {
setErrorMsg("Name and email are required. If you prefer not to provide them, check the box to send anonymously.");
return;
}
if (data.message.length < 1) {
setErrorMsg("What do you want to tell me???");
return;
}
setStatus('loading');
//process
await new Promise(res => setTimeout(res, 2000));
setStatus('success');
}
return (
<form action="POST" className="space-y-4" ref={formRef} onSubmit={send}>
<FloatingLabel placeholder="Leave an anonymous message, but I won't answer it">
<CheckboxInput
name="anon"
placeholder="Send anonymously🤫"
onChange={setAnon}
/>
</FloatingLabel>
{!anon && <>
<label className="block" htmlFor="contact-name">
<div className="mb-2">Name</div>
<Input
id="contact-name"
className="w-full"
name="name"
type="text"
placeholder="Any name you want me to know"
/>
</label>
<label className="block" htmlFor="contact-email">
<div className="mb-2">Email</div>
<Input
id="contact-email"
className="w-full"
name="email"
type="email"
/>
</label>
</>}
<label className="block" htmlFor="contact-msg">
<div className="mb-2">Message</div>
<textarea
name="message"
id="contact-msg"
className="max-h-96 h-32 w-full p-3 bg-background border border-primary"
placeholder="Tell me something cool, or ask question"
/>
</label>
<div className="text-primary">{errorMsg}</div>
<Submit className="w-full">{statusMsg[status]}</Submit>
</form>
);
}

View File

@ -0,0 +1,49 @@
'use client'
import clsx from "clsx";
import { Icon } from "@iconify/react";
import { useState } from "react";
export function Input ({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
className={clsx("block py-3 px-4 bg-background text-white border border-primary", className)}
{...props}
/>
)
}
export function Submit ({ className, type, ...props }: React.ComponentProps<'button'>) {
return (
<button
type={'submit'}
className={clsx("block py-3 px-4 bg-primary text-background", className)}
{...props}
/>
)
}
interface CheckboxInputProps {
name: string,
placeholder: string,
onChange: (value: boolean) => void
}
export function CheckboxInput ({ placeholder, name, onChange }: CheckboxInputProps) {
const [checked, setCheck] = useState(false);
const onClick = () => {
setCheck(c => !c);
onChange(!checked);
}
return (
<div className="">
<button type="button" id={`checkbox-${name}`} className="inline-block bg-none border border-primary w-4 h-4" onClick={onClick}>
{checked && <Icon icon="lucide:check" className="text-primary" />}
</button>
<label htmlFor={`checkbox-${name}`} className="ps-2 inline-block">{placeholder}</label>
</div>
)
}

View File

@ -2,11 +2,11 @@ import Image from "next/image"
import { FloatingLabel } from "./floating-label"
export const LandingImage = () => (
<FloatingLabel placeholder="Coral Glasses ❤️. Alert: NOT MY WORK! See the end of the page">
<FloatingLabel placeholder="ENA ❤️. Alert: NOT MY WORK!">
<Image
className="mb-8 mx-auto"
className="mb-8 mx-auto h-auto"
alt="Coral <3"
src="https://media1.tenor.com/m/RIP2rxKM_FgAAAAC/ena-ena-dream-bbq.gif"
src="https://media.tenor.com/1BCeG1aTiBAAAAAd/temptation-stairway-ena.gif"
width={280}
height={280}
unoptimized

View File

@ -1,6 +1,7 @@
import Image from "next/image"
import Link from "@/components/link";
import { FloatingLabel } from "@/components/floating-label";
import ContactForm from "@/components/form/contact-form";
Welcome!
@ -8,6 +9,8 @@ This is our cozy little corner of the internet where we run services and website
We've got tools, resources, game server and other stuff that just works and doesn't burn our wallet, maybe.
I love putting silly favorite characters on this site
## About Me
Nomi Nonszy (also known as Nonszy, Nomi Nonsense, whatever).
@ -15,9 +18,9 @@ I write code, do some art, and make sure our server doesn't catch on fire lol.
I build sick web apps with modern stacks. Big fan of open-source stuff.
Hate something that make things overengineered and boilerplate, but still use them anyway.
I only play indie games, and cooking up cursed mods just for fun.
Love with indie games, and cooking up cursed mods just for fun.
Multifandom with Psychopomp, Deltarune, and ENA! I'm currently dedicated to the ENA SERIES!! Especially <Link href="https://enajoelg.fandom.com/wiki/Coral_Glasses" target="_blank">Coral Glasses</Link> my beloved wife ❤️
Multifandom with Psychopomp, Deltarune, and ENA! I'm currently dedicated to the ENA SERIES!!
<Image
className="mb-12 mx-auto"
@ -87,6 +90,7 @@ and published under the ENA Team. She speaks Korean in the game and is voiced by
The game is part of the surreal and artistically distinct ENA universe, which expands upon his earlier animated web series of the same name.
She's supposed to handle business stuff, but she's sweating, faxing out of her hairline, and spiraling into mild panic every five minutes
She's cute, anxious, awkward, weird, beautiful and i swear, she's literally me at work sweating through every conversation ashdjakwoiqhkaslchmaujqk
## Tell me something
<ContactForm />

View File

@ -7,7 +7,7 @@ const events: EventsDate[] = [
{
name: 'christmas',
start: [12, 20],
end: [12, 30]
end: [12, 32]
},
]