Files
lmgcitfy/app/page.tsx
2026-02-14 00:34:05 +00:00

296 lines
8.3 KiB
TypeScript

"use client";
import theme from "@/theme";
import {
Button,
ChakraProvider,
Container,
Flex,
Heading,
Image,
Input,
Text,
Box,
Separator,
} from "@chakra-ui/react";
import { useEffect, useState, useRef, Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { toaster } from "@/components/ui/toaster";
import { getRandomResultFromGCI, lmgcitfy } from "@/actions";
export default function Home() {
// state shit
const [query, setQuery] = useState<string>("");
const [loading, setLoading] = useState(false);
const [link, setLink] = useState<string>("");
// animation refs
const inputRef = useRef<HTMLInputElement>(null);
const normalButtonRef = useRef<HTMLButtonElement>(null);
const luckyButtonRef = useRef<HTMLButtonElement>(null);
const cursorRef = useRef<HTMLImageElement>(null);
// query shit
const passedInParams = useSearchParams();
const passedInQuery = passedInParams.get("query");
const isFeelingLucky = passedInParams.get("lucky");
// handle shit
const handleGenerateLink = async (feelingLucky: boolean) => {
if (!query) {
toaster.error({ description: "You must provide a search query." });
return;
}
const queryParams = new URLSearchParams();
queryParams.append("query", query);
try {
setLoading(true);
let result: APIResult<string>;
if (feelingLucky) {
queryParams.append("lucky", "true");
result = await getRandomResultFromGCI(query);
} else {
result = await lmgcitfy(query);
}
if (result.error) {
toaster.create({
type: "error",
title: "Error",
description: result.errorMessage,
});
return;
}
setLink(
`${window.location.protocol}//${window.location.host}/?${queryParams.toString()}`,
);
} catch (error) {
if (error instanceof Error) {
toaster.error({ description: error.message });
return;
}
toaster.error({ description: "Something went wrong." });
return;
} finally {
setLoading(false);
}
};
const handleCursorMove = () => {
requestAnimationFrame(() => {
// we have to stack this because browser paints the first render, then we request twice to paint the third render which applies the necessary changes for the transition (gay)
requestAnimationFrame(() => {
const input = inputRef.current!.getBoundingClientRect();
const inputTargetWidth = input.x + input.width / 3;
const inputTargetHeight = input.y + input.height / 2;
cursorRef.current!.style.left = `${inputTargetWidth}px`;
cursorRef.current!.style.top = `${inputTargetHeight}px`;
});
});
};
useEffect(() => {
if (!passedInQuery) {
return;
}
const handleMoveToButton = () => {
let buttonRef;
if (isFeelingLucky === "true") {
buttonRef = luckyButtonRef;
} else {
buttonRef = normalButtonRef;
}
const boundingRect = buttonRef.current!.getBoundingClientRect();
const buttonX = boundingRect.x + boundingRect.width / 2;
const buttonY = boundingRect.y + boundingRect.height / 2;
cursorRef.current!.style.left = `${buttonX}px`;
cursorRef.current!.style.top = `${buttonY}px`;
setTimeout(() => {
buttonRef.current!.style.scale = `0.95`;
}, 1250);
setTimeout(() => {
buttonRef.current!.style.scale = `1`;
}, 1750);
setTimeout(() => {
toaster.create({
type: "success",
title: "LMGCITFY",
description: "Was that so hard?",
});
}, 3000);
};
const handleTypingAnimation = () => {
setTimeout(() => {
const chars = passedInQuery!.split("");
let counter = 0;
const intervalId = setInterval(() => {
if (inputRef.current!.value.length < chars.length) {
const splitValue = inputRef.current!.value.split("");
splitValue.push(chars[counter]);
inputRef.current!.value = splitValue.join("");
counter++;
}
if (chars.length === inputRef.current!.value.length) {
clearInterval(intervalId);
handleMoveToButton();
}
}, 75);
}, 2000);
};
const getGCILink = async () => {
let result: APIResult<string>;
if (isFeelingLucky === "true") {
result = await getRandomResultFromGCI(passedInQuery);
} else {
result = await lmgcitfy(passedInQuery);
}
if (result.error) {
toaster.create({
type: "error",
title: "Error",
description: result.errorMessage,
});
return;
}
const link = result.payload;
setTimeout(() => (window.location.href = link), 9000);
};
handleCursorMove();
handleTypingAnimation();
getGCILink();
}, [passedInQuery, isFeelingLucky]);
return (
<ChakraProvider value={theme}>
<Image
src={"/cursor.png"}
height={"24px"}
alt="Fake cursor"
ref={cursorRef}
visibility={passedInParams.toString().length > 0 ? "visible" : "hidden"}
position={"fixed"}
top={0}
left={0}
zIndex={1000}
transition={"top 2s, left 2s"}
/>
<Container
backgroundColor={"#2a2a2a"}
borderRadius={"lg"}
boxShadow={"0 0 12px #4a4a4a"}
py={4}
width={{ base: "90%", md: "60%" }}
overflow={"hidden"}
animationStyle={"scale-fade-in"}
animationDuration={"slowest"}
>
<Flex justify={"center"} align={"center"} p={2} mb={4}>
<Image
src={"/gci_logo_large.png"}
height={{ base: "50px", md: "75px" }}
alt="GunCAD Index logo"
/>
<Flex flexDir={"column"} mx={4}>
<Heading
size={{ base: "2xl", md: "5xl" }}
fontFamily={"var(--font-ibm)"}
lineHeight={1}
>
GunCAD Index
</Heading>
<Text
fontSize={{ base: "sm", md: "xl" }}
ml={{ md: 1 }}
fontFamily={"var(--font-ibm)"}
>
A search engine for guns.
</Text>
</Flex>
</Flex>
<Input
variant={"outline"}
borderRadius={"lg"}
border={"1px solid #4a4a4a"}
size={"lg"}
placeholder="Enter your query here"
ref={inputRef}
onChange={(e) => setQuery(e.target.value)}
/>
<Flex
justify={"space-around"}
gapY={4}
flexDir={{ base: "column", md: "row" }}
p={2}
my={4}
>
<Button
colorPalette={"green"}
variant={"solid"}
size={"lg"}
onClick={() => {
handleGenerateLink(false);
}}
loading={loading}
ref={normalButtonRef}
>
Search on GCI
</Button>
<Button
colorPalette={"white"}
variant={"outline"}
size={"lg"}
border={"1px solid #4a4a4a"}
color={"#FFF"}
onClick={() => {
handleGenerateLink(true);
}}
loading={loading}
ref={luckyButtonRef}
>
I&apos;m feeling free
</Button>
</Flex>
<Box
display={link ? "block" : "none"}
animationStyle={link ? "slide-fade-in" : undefined}
animationDuration={"slowest"}
>
<Separator
background={
"linear-gradient(to right, transparent 0%, #FFFFFF60 50%, transparent 100%)"
}
mb={4}
height={"2px"}
/>
<Flex flexDirection={"column"} gapY={4}>
<Input
type="text"
readOnly={true}
value={link}
border={"1px solid #4a4a4a"}
/>
<Button
variant={"outline"}
border={"1px solid #4a4a4a"}
color={"#FFF"}
onClick={() => {
navigator.clipboard.writeText(link);
toaster.create({ description: "Copied link to clipboard" });
}}
>
Copy Link
</Button>
</Flex>
</Box>
</Container>
</ChakraProvider>
);
}