new site
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import InputBox from './InputBox';
|
||||
import { submitContact } from '@/actions/contact';
|
||||
|
||||
export default function ContactForm() {
|
||||
const [ error, setError ] = useState<string>("");
|
||||
const [ success, setSuccess ] = useState<boolean>(false);
|
||||
const [ pending, setPending ] = useState<boolean>(false);
|
||||
|
||||
const handleSubmission = async (formData: FormData) => {
|
||||
setPending(true)
|
||||
|
||||
try {
|
||||
const result = await submitContact(formData)
|
||||
|
||||
if (result.error || !result.success) {
|
||||
setError('An unexpected error occurred')
|
||||
} else setSuccess(true)
|
||||
|
||||
const form = document.getElementById('contact-form') as HTMLFormElement
|
||||
form.reset()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setError('An unexpected error occurred')
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setError("")
|
||||
}, 6000)
|
||||
}, [error]);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setSuccess(false)
|
||||
}, 6000)
|
||||
}, [success]);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg p-2 w-full md:w-[50%] mx-auto">
|
||||
<h1 className="text-2xl font-bold text-center pb-8">Contact Me</h1>
|
||||
|
||||
<form id="contact-form" action={handleSubmission} className="space-y-2 text-center">
|
||||
<InputBox
|
||||
isText={true}
|
||||
name="name"
|
||||
placeholder="Your name.."
|
||||
disabled={pending}
|
||||
/>
|
||||
<InputBox
|
||||
name="email"
|
||||
type="email"
|
||||
isText={true}
|
||||
placeholder="your@supercool.email"
|
||||
disabled={pending}
|
||||
/>
|
||||
<InputBox
|
||||
isText={true}
|
||||
name="subject"
|
||||
placeholder="Subject.."
|
||||
disabled={pending}
|
||||
/>
|
||||
<InputBox
|
||||
isText={false}
|
||||
name="message"
|
||||
placeholder="Your message here.."
|
||||
disabled={pending}
|
||||
/>
|
||||
|
||||
<div className="block text-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-white w-full text-black font-bold rounded-lg max-w-fit p-2"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
|
||||
{
|
||||
error && (
|
||||
<p className="text-red-400 font-bold text-center pt-2">{error}</p>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
success && (
|
||||
<p className="font-bold text-green-500 text-center pt-2">Success! Your message has been received</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function CustomButton({
|
||||
to,
|
||||
label
|
||||
}: {
|
||||
to: string,
|
||||
label: string
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Link href={to} target="_blank">
|
||||
<div className={`bg-black text-white text-lg font-semibold text-left p-2 w-52 rounded-lg`}>
|
||||
<div className="px-1 space-x-2">
|
||||
<div className="inline">
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import Image from "next/image"
|
||||
|
||||
export default async function DataComponent({
|
||||
url,
|
||||
header,
|
||||
projectHeader,
|
||||
projectSubheader,
|
||||
projectImage
|
||||
}: {
|
||||
url: string,
|
||||
header: string,
|
||||
projectHeader: string,
|
||||
projectSubheader: string,
|
||||
projectImage?: string
|
||||
}) {
|
||||
return (
|
||||
<a href={url} target="_blank">
|
||||
<div className="bg-white rounded-lg text-black p-2">
|
||||
<h1 className="text-center font-semibold">{header}</h1>
|
||||
|
||||
<div className="block">
|
||||
<div className="flex items-center">
|
||||
{
|
||||
projectImage && <Image alt="Project Image" src={projectImage} width={100} height={100} className="w-[80px] h-[50px] object-cover" />
|
||||
}
|
||||
|
||||
<div className="flex flex-col mx-2 min-w-0 flex-grow">
|
||||
<h1 className="font-bold text-left truncate">{projectHeader}</h1>
|
||||
<h1 className="text-left truncate">{projectSubheader}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import DataComponent from '../DataComponent';
|
||||
import type { ContentTrackerItem } from '@/utils/types';
|
||||
|
||||
const fetchData = async () => {
|
||||
const { data } = await fetch("https://api.alexav.gg/v4/tracker/latest/book", {
|
||||
method: 'get',
|
||||
next: { revalidate: 60 }
|
||||
}).then((res) => res.json())
|
||||
return data
|
||||
}
|
||||
|
||||
export default async function RecentBook() {
|
||||
const data = await fetchData() as ContentTrackerItem
|
||||
|
||||
const getDateString = (startedOn: Date, finished: boolean) => {
|
||||
if (startedOn && !finished) {
|
||||
const startedOnDate = new Date(startedOn);
|
||||
return "Started on " + startedOnDate.toLocaleDateString();
|
||||
} else if (finished) {
|
||||
return "Finished!"
|
||||
} else {
|
||||
return "Just Added"
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm reading"}
|
||||
projectHeader={data.title}
|
||||
projectSubheader={getDateString(data.startedOn, data.finished)}
|
||||
url={"https://tracker.alexav.gg/?type=book&id=" + data.id}
|
||||
projectImage={data.image}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={"No data :("}
|
||||
projectSubheader={"Nothing was returned"}
|
||||
url={"/"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import DataComponent from '../DataComponent';
|
||||
import type { MedalClip } from '@/utils/types';
|
||||
|
||||
const fetchData = async () => {
|
||||
const { data } = await fetch("https://api.alexav.gg/v4/social/medal", {
|
||||
method: 'get',
|
||||
next: { revalidate: 60 }
|
||||
}).then((res) => res.json())
|
||||
return data[0]
|
||||
}
|
||||
|
||||
export default async function RecentClip() {
|
||||
const data = await fetchData() as MedalClip
|
||||
|
||||
const translateToWebsiteUrl = (url: string) => {
|
||||
return url.replace('/clip/', "/clips/")
|
||||
}
|
||||
|
||||
// i'm a neat man what can i say
|
||||
const likeOrLikes = (likes: number) => {
|
||||
return (likes > 1 && likes !== 0) ? "likes" : "like"
|
||||
}
|
||||
const viewOrViews = (views: number) => {
|
||||
return (views > 1 && views !== 0) ? "views" : "view"
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<DataComponent
|
||||
url={translateToWebsiteUrl(data.directClipUrl)}
|
||||
header={"Clips I've taken"}
|
||||
projectHeader={data.contentTitle}
|
||||
projectSubheader={`${data.contentLikes} ${likeOrLikes(data.contentLikes)} | ${data.contentViews} ${viewOrViews(data.contentViews)}`}
|
||||
projectImage={data.contentThumbnail}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={"No data :("}
|
||||
projectSubheader={"Nothing was returned"}
|
||||
url={"/"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import DataComponent from '../DataComponent';
|
||||
import type { GameData } from '@/utils/types';
|
||||
|
||||
const fetchData = async () => {
|
||||
const { data } = await fetch("https://api.alexav.gg/v4/game", {
|
||||
method: 'get',
|
||||
next: { revalidate: 10 }
|
||||
}).then((res) => res.json())
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export default async function RecentGame() {
|
||||
const data = await fetchData() as GameData
|
||||
|
||||
const getTime = (time: number) => {
|
||||
const date = new Date(time*1000);
|
||||
return `${date.toLocaleDateString()} at ${date.toLocaleTimeString()}`
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<DataComponent
|
||||
url={"https://medal.tv/u/alexav"}
|
||||
header={"Games I'm playing"}
|
||||
projectHeader={data.name}
|
||||
projectSubheader={
|
||||
data.finished ? `Last played on ${getTime(data.startedOn)}`
|
||||
: `Started on ${getTime(data.startedOn)}`
|
||||
}
|
||||
projectImage={"https://storage.alexav.gg/content/933d5523-5e20-4040-9fe9-b73552eac8ab.png"}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={"No data :("}
|
||||
projectSubheader={"Nothing was returned"}
|
||||
url={"/"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import DataComponent from '../DataComponent';
|
||||
import type { MusicData } from '@/utils/types';
|
||||
|
||||
const fetchData = async () => {
|
||||
const { data } = await fetch("https://api.alexav.gg/v4/social/music", {
|
||||
method: 'get',
|
||||
next: { revalidate: 60 }
|
||||
}).then((res) => res.json())
|
||||
|
||||
return data.tracks[0]
|
||||
}
|
||||
|
||||
export default async function RecentListen() {
|
||||
const data = await fetchData() as MusicData
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<DataComponent
|
||||
url={"https://last.fm/user/lulawex"}
|
||||
header={"Music I'm listening to"}
|
||||
projectHeader={data.name}
|
||||
projectSubheader={data.artist["#text"]}
|
||||
projectImage={data.image.filter((v) => v.size === "medium")[0]["#text"]}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={"No data :("}
|
||||
projectSubheader={"Nothing was returned"}
|
||||
url={"/"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import DataComponent from '../DataComponent';
|
||||
import type { WatchData } from '@/utils/types';
|
||||
|
||||
const fetchData = async () => {
|
||||
const data = await fetch("https://api.alexav.gg/v4/tracker/latest/tv", {
|
||||
method: 'get',
|
||||
next: { revalidate: 60 }
|
||||
});
|
||||
return await data.json()
|
||||
}
|
||||
|
||||
export default async function RecentWatch() {
|
||||
const data = await fetchData() as WatchData
|
||||
|
||||
const getSubheaderText = () => {
|
||||
if (data?.data.finished) {
|
||||
return "Finished!"
|
||||
} else if (data?.data.startedOn) {
|
||||
return "Last update on " + new Date(data.data.updatedAt).toLocaleDateString()
|
||||
} else {
|
||||
return "Just Added"
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={data.data.title}
|
||||
projectSubheader={ getSubheaderText() }
|
||||
url={"https://tracker.alexav.gg/?type=tv&id=" + data.data.id}
|
||||
projectImage={data.data.image}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DataComponent
|
||||
header={"What I'm watching"}
|
||||
projectHeader={"No data :("}
|
||||
projectSubheader={"Nothing was returned"}
|
||||
url={"/"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function Footer() {
|
||||
// states
|
||||
const [ mailState, setMailState ] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mailState) {
|
||||
setTimeout(() => {
|
||||
setMailState(false)
|
||||
}, 3000)
|
||||
}
|
||||
}, [mailState])
|
||||
|
||||
return (
|
||||
<div className="lg:w-1/2 md:w-full mx-auto rounded-t-lg text-center p-4 select-none">
|
||||
<h1 className="font-bold text-sm">© {new Date().getFullYear()}, Alex Frantz. All Rights Reserved</h1>
|
||||
<p>All logos & images on this page belong to their respective owners.</p>
|
||||
|
||||
<hr className="my-2 w-1/2 mx-auto" />
|
||||
|
||||
{
|
||||
mailState ? (
|
||||
<p className="font-bold hover:underline max-w-fit mx-auto">
|
||||
Copied!
|
||||
</p>
|
||||
) : (
|
||||
<button onClick={() => {
|
||||
setMailState(true)
|
||||
navigator.clipboard.writeText("alex@vierfrantz.com")
|
||||
}} className="hover:underline">alex@vierfrantz.com</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client"
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from "next/link"
|
||||
|
||||
export default function Header() {
|
||||
const quoteArray = [
|
||||
"It works on my machine",
|
||||
"Revert 'Revert 'Revert",
|
||||
"'fuck the tests'",
|
||||
"this is so cooked",
|
||||
"Professional Production Tester",
|
||||
"I break hearts to save lives.",
|
||||
"go bills",
|
||||
"josh allen is my goat",
|
||||
"lets go buffalo",
|
||||
"oorah",
|
||||
"how bout them bills??",
|
||||
"where else would you rather be..",
|
||||
"..than right here, right now?!"
|
||||
]
|
||||
const [ quote, setQuote ] = useState<string>(quoteArray[0])
|
||||
|
||||
useEffect(() => {
|
||||
setQuote(quoteArray[Math.floor(Math.random() * quoteArray.length)]);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-900 p-4 flex justify-between">
|
||||
<div>
|
||||
<Link href="/" className="hover:underline">
|
||||
<h1 className="text-2xl font-bold">Alex Frantz</h1>
|
||||
</Link>
|
||||
<p onClick={() => {
|
||||
setQuote(quoteArray[Math.floor(Math.random() * quoteArray.length)])
|
||||
}} className="opacity-50 italic select-none">{quote}</p>
|
||||
</div>
|
||||
<div className="space-x-2 items-center justify-center flex select-none">
|
||||
<a className="hover:underline" href="#projects">Projects</a>
|
||||
<a className="hover:underline" href="#data">Data</a>
|
||||
<a className="hover:underline" href="#contact">Contact</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
export const GitHub = () => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-7 mx-auto bg-white p-1 rounded-lg inline"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205
|
||||
11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633
|
||||
17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305
|
||||
3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176
|
||||
0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12
|
||||
3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015
|
||||
3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const LastFM = () => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-7 mx-auto bg-white p-1 rounded-lg inline"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Last.fm</title>
|
||||
<path d="M10.584 17.21l-.88-2.392s-1.43 1.594-3.573 1.594c-1.897 0-3.244-1.649-3.244-4.288 0-3.382 1.704-4.591 3.381-4.591
|
||||
2.42 0 3.189 1.567 3.849 3.574l.88 2.749c.88 2.666 2.529 4.81 7.285 4.81 3.409 0 5.718-1.044 5.718-3.793
|
||||
0-2.227-1.265-3.381-3.63-3.931l-1.758-.385c-1.21-.275-1.567-.77-1.567-1.595 0-.934.742-1.484 1.952-1.484 1.32 0 2.034.495 2.144
|
||||
1.677l2.749-.33c-.22-2.474-1.924-3.492-4.729-3.492-2.474 0-4.893.935-4.893 3.932 0 1.87.907 3.051 3.189 3.601l1.87.44c1.402.33
|
||||
1.869.907 1.869 1.704 0 1.017-.99 1.43-2.86 1.43-2.776 0-3.93-1.457-4.59-3.464l-.907-2.75c-1.155-3.573-2.997-4.893-6.653-4.893C2.144
|
||||
5.333 0 7.89 0 12.233c0 4.18 2.144 6.434 5.993 6.434 3.106 0 4.591-1.457 4.591-1.457z"/></svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Twitter = () => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-7 mx-auto bg-white p-1 rounded-lg inline"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>X</title>
|
||||
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0
|
||||
1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Medal = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-7 mx-auto bg-white p-1 rounded-lg inline" x="0px" y="0px" viewBox="0,0,256,256">
|
||||
<g fill="none" fillRule="nonzero" stroke="none" strokeWidth="1" strokeLinecap="butt" strokeLinejoin="miter" strokeMiterlimit="10"
|
||||
strokeDasharray="" strokeDashoffset="0" fontFamily="none" fontWeight="none" fontSize="none" textAnchor="none"
|
||||
>
|
||||
<g transform="scale(5.33333,5.33333)">
|
||||
<path d="M40,18.723l-8.239,4.777l8.239,4.777z" fill="none">
|
||||
</path>
|
||||
<path d="M8,28.277l8.239,-4.777l-8.239,-4.777z" fill="none">
|
||||
</path>
|
||||
<path d="M8,18.723l8.239,4.777l7.761,-4.5l-16,-9l-8,4.085v18.83l8,-4.638z" fill="#000000">
|
||||
</path>
|
||||
<path d="M40,10l-16,9l7.761,4.5l8.239,-4.777v9.554l8,4.638v-18.83z" fill="#000000">
|
||||
</path><path d="M31.761,23.5l-7.761,4.5l16,9v-8.723z" fill="#000000">
|
||||
</path><path d="M8,28.277v8.723l16,-9l-7.761,-4.5z" fill="#000000">
|
||||
</path>
|
||||
<path d="M0,32.915l8,4.085v-8.723z" fill="#060606"></path>
|
||||
<path d="M24,19l-7.761,4.5l7.761,4.5l7.761,-4.5z" fill="#060606">
|
||||
</path>
|
||||
<path d="M40,37l8,-4.085l-8,-4.638z" fill="#060606">
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Link = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-7 h-7 mx-auto bg-white p-1 rounded-lg inline" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="none" stroke="black" strokeWidth="2" d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||
<path fill="none" stroke="black" strokeWidth="2" d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const LinkedIn = () => {
|
||||
return (
|
||||
<svg className="w-7 h-7 mx-auto bg-white p-1 rounded-lg inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="#000000"
|
||||
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Bluesky = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-7 h-7 mx-auto bg-white p-1 rounded-lg inline" viewBox="0 0 568 501" width="568" height="501">
|
||||
<path
|
||||
fill="black"
|
||||
d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 375.812 284.017 372.431 284 375.306C283.983 372.431 282.831 375.812 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
export default function InputBox({
|
||||
name,
|
||||
placeholder = "",
|
||||
isText = true,
|
||||
disabled = false,
|
||||
type = "text",
|
||||
onChange
|
||||
}: {
|
||||
name: string,
|
||||
isText?: boolean,
|
||||
disabled?: boolean,
|
||||
placeholder?: string,
|
||||
type?: string,
|
||||
onChange?: (value: string) => void
|
||||
}) {
|
||||
if (isText) {
|
||||
return (
|
||||
<div className="block">
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
className="rounded-lg p-2 w-1/2 text-black"
|
||||
onChange={onChange ? ({target}) => onChange(target.value) : undefined}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="block">
|
||||
<textarea
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange ? ({ target }) => onChange(target.value) : undefined}
|
||||
disabled={disabled}
|
||||
className="rounded-lg p-2 w-1/2 h-32 text-black"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type MusicObject = {
|
||||
artist: {
|
||||
mbid: string,
|
||||
"#text": string
|
||||
},
|
||||
name: string,
|
||||
image: ImageObject[],
|
||||
url: string
|
||||
}
|
||||
type ImageObject = {
|
||||
size: "small" | "medium" | "large" | "extralarge",
|
||||
"#text": string
|
||||
}
|
||||
|
||||
export default function MusicComponent() {
|
||||
const [ music, setMusic ] = useState<MusicObject | null>(null);
|
||||
const [ failedFetch, setFailedFetch ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMusic() {
|
||||
const response = await fetch("https://api.alexav.gg/v4/social/music")
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setMusic(data.data.tracks[0] as MusicObject)
|
||||
} else setFailedFetch(true)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
fetchMusic()
|
||||
}, 60000)
|
||||
fetchMusic()
|
||||
}, [])
|
||||
|
||||
if (music) {
|
||||
return (
|
||||
<div className="flex align-middle">
|
||||
<img width="50" src={music.image[1]['#text']} />
|
||||
<div className="pl-2">
|
||||
<h1>{music.name}</h1>
|
||||
<p>{music.artist['#text']}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
if (failedFetch) {
|
||||
return (
|
||||
<div>
|
||||
<h1>uh oh!</h1>
|
||||
<p>failed to get music :(</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<h1>loading music..</h1>
|
||||
<p>loading artist..</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
export default function ProjectComponent({
|
||||
urlTo,
|
||||
description,
|
||||
name,
|
||||
tags
|
||||
}: {
|
||||
urlTo: string,
|
||||
description: string,
|
||||
name: string,
|
||||
tags: string[]
|
||||
}) {
|
||||
return (
|
||||
<a href={urlTo} target="_blank">
|
||||
<div className="bg-white text-black mx-auto rounded-lg overflow-hidden">
|
||||
<div className="p-4 space-y-2">
|
||||
<h1 className="font-bold text-xl">{name}</h1>
|
||||
<p>{description}</p>
|
||||
|
||||
<div className="space-x-2">
|
||||
{
|
||||
tags.map((v, k) => (
|
||||
<div className="bg-zinc-600 inline max-w-fit p-[5px] text-xs font-bold rounded-xl bg-opacity-20" key={k}>{v}</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type TVObject = {
|
||||
id: number,
|
||||
type: string,
|
||||
title: string,
|
||||
author: string | null,
|
||||
description: string,
|
||||
image: string,
|
||||
episode: string,
|
||||
tmdbId: string,
|
||||
finished: boolean,
|
||||
yearReleased: string,
|
||||
genre: string,
|
||||
startedOn: string
|
||||
}
|
||||
|
||||
export default function TVComponent() {
|
||||
const [ show, setShow ] = useState<TVObject | null>(null);
|
||||
const [ failedFetch, setFailedFetch ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMusic() {
|
||||
const response = await fetch("https://api.alexav.gg/v4/tracker/latest/tv")
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setShow(data.data as TVObject)
|
||||
} else setFailedFetch(true)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
fetchMusic()
|
||||
}, 60000)
|
||||
fetchMusic()
|
||||
}, [])
|
||||
|
||||
if (show) {
|
||||
return (
|
||||
<div className="flex align-middle">
|
||||
<a className="hover:underline" href={`https://tracker.alexav.gg/?id=${show.id}&type=${show.type}`} target="_blank">
|
||||
<h1 className="lowercase">{show.title} - {show.finished ? "finished" : show.episode}</h1>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
if (failedFetch) {
|
||||
return (
|
||||
<div>
|
||||
<p>failed to get show :(</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<p>loading show..</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user