mhsf-dev/apps/www/src/components/feat/embeds/embed.tsx

218 lines
8.0 KiB
TypeScript
Raw Normal View History

2025-02-13 21:48:41 -06:00
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
2025-02-13 21:48:41 -06:00
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
"use client";
import IconDisplay from "@/components/feat/icons/minecraft-icon-display";
2025-02-13 21:48:41 -06:00
import { Badge } from "@/components/ui/badge";
import type { ServerResponse } from "@/lib/types/mh-server";
2025-02-15 15:58:50 -06:00
import { Check, Copy, ExternalLink, ServerCrash } from "lucide-react";
2025-02-13 21:48:41 -06:00
import { notFound, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
2025-02-13 21:48:41 -06:00
import useClipboard from "@/lib/useClipboard";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
import Link from "next/link";
2025-02-15 15:58:50 -06:00
import { AnimatePresence, motion } from "framer-motion";
2025-02-13 21:48:41 -06:00
export default function Embed({ params }: { params: { server: string } }) {
const [serverFound, setServerFound] = useState(true);
const [loading, setLoading] = useState(true);
const [copied, setCopied] = useState(false);
const [serverObject, setServerObject] = useState<ServerResponse | null>(null);
const searchParams = useSearchParams();
const staticMode = searchParams?.get("static") === "true";
const noShowBranding = searchParams?.get("branding") === "false";
const clipboard = useClipboard();
useEffect(() => {
(async () => {
const serverFoundResponse = await fetch(
`https://api.minehut.com/server/${params.server}?byName=true`
2025-02-13 21:48:41 -06:00
);
const stream = await serverFoundResponse.json();
if (stream.server == null) setServerFound(false);
else setServerObject(stream.server);
setLoading(false);
})();
}, [params]);
if (loading) {
return <Spinner />;
2025-02-13 21:48:41 -06:00
}
if (!serverFound) {
notFound();
}
return (
<div className="rounded w-[390px] h-[145px] bg-muted">
<Link
className={cn(
"flex items-center text-sm cursor-pointer border-b p-2",
staticMode ? "" : "group"
)}
href={`/server/${params.server}`}
target="_blank"
2025-02-13 21:48:41 -06:00
>
<ServerCrash
size={16}
className="group-hover:text-white p-[4px] group-hover:p-[3px] w-[24px] h-[24px] transition-all bg-gradient-to-r group-hover:from-blue-600 group-hover:to-purple-500 group-hover:rounded"
/>
<span className="transition-colors ml-2 group-hover:bg-clip-text group-hover:text-transparent bg-gradient-to-r group-hover:from-blue-600 group-hover:to-purple-500">
Powered by MHSF
</span>
</Link>
2025-02-13 21:48:41 -06:00
<div className="px-4 pt-2 flex items-center group overflow-hidden">
2025-02-15 15:58:50 -06:00
<div
className={cn(
staticMode ? "block" : "opacity-0 group-hover:opacity-100",
"transform transition-all duration-300 ease-in-out",
"group-hover:translate-x-0 -translate-x-full",
"absolute left-[10px] w-0 group-hover:w-[64px]"
)}
>
2025-02-13 21:48:41 -06:00
<Tooltip>
<TooltipTrigger>
<Button
variant="secondary"
2025-02-13 21:48:41 -06:00
size="sm"
className="mb-1"
onClick={() => {
setCopied(true);
clipboard.writeText(`${params.server}.mhsf.minehut.gg"`);
2025-02-13 21:48:41 -06:00
setTimeout(() => setCopied(false), 1000);
}}
>
2025-02-15 15:58:50 -06:00
<div className="relative w-full h-full grid place-items-center">
<AnimatePresence>
{copied ? (
<motion.div
key="check"
animate={{ opacity: 1, scale: 1 }}
initial={{ opacity: 0, scale: 0.3 }}
exit={{ opacity: 0, scale: 0.3 }}
transition={{ duration: 0.2, ease: "linear" }}
className="top-0 left-0"
style={{ gridRow: 1, gridColumn: 1 }}
>
<Check size={18} />
</motion.div>
) : (
<motion.div
key="clipboard"
animate={{ opacity: 1, scale: 1 }}
initial={{ opacity: 0, scale: 0.3 }}
exit={{ opacity: 0, scale: 0.3 }}
transition={{ duration: 0.2, ease: "linear" }}
className="top-0 left-0"
style={{ gridRow: 1, gridColumn: 1 }}
>
<Copy size={18} />
</motion.div>
)}
</AnimatePresence>
</div>
2025-02-13 21:48:41 -06:00
</Button>
</TooltipTrigger>
<TooltipContent>Copy this server IP</TooltipContent>
</Tooltip>{" "}
<br />
<Button
variant="secondary"
2025-02-13 21:48:41 -06:00
size="sm"
onClick={() => {
window.open(`/server/${params.server}`, "_blank")?.focus();
2025-02-13 21:48:41 -06:00
}}
>
<ExternalLink size={16} />
</Button>
</div>
2025-02-15 15:58:50 -06:00
<div
className={cn(
"flex items-center transition-all duration-300 ease-in-out",
staticMode ? "ml-0" : "group-hover:ml-[42px]"
)}
>
{serverObject && (
<IconDisplay
server={serverObject}
className={cn(
"flex items-center mr-2",
staticMode ? "mb-1" : "group-hover:mb-1"
2025-02-13 21:48:41 -06:00
)}
2025-02-15 15:58:50 -06:00
/>
2025-02-13 21:48:41 -06:00
)}
2025-02-15 15:58:50 -06:00
<div className="block">
<strong className="text-lg flex items-center gap-2">
{params.server}
{!noShowBranding && <Badge variant="blue">on Minehut</Badge>}
</strong>
<span className="text-sm">Joined {serverObject?.joins} times</span>
{serverObject?.online && (
<span className="flex items-center">
{serverObject.playerCount === 0 ? (
<div
className="items-center border"
style={{
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
) : (
<div
className="items-center"
style={{
backgroundColor: "#0cce6b",
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
)}
<span className="text-sm ml-1">
{serverObject.playerCount} player(s) online
</span>
</span>
)}
</div>
2025-02-13 21:48:41 -06:00
</div>
</div>
</div>
);
}