added spectating
This commit is contained in:
parent
b778796195
commit
33c54983d4
|
|
@ -1,2 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /
|
||||
Allow: /
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { Button } from "./Button";
|
||||
import { useState } from "react";
|
||||
|
||||
interface ShareButtonProps {
|
||||
gameId: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ShareButton: React.FC<ShareButtonProps> = ({ gameId, className }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleShare = async () => {
|
||||
const shareUrl = `${window.location.origin}/play/${gameId}`;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
// Fallback for browsers that don't support clipboard API
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = shareUrl;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleShare}
|
||||
className={className}
|
||||
title="Share game for spectating"
|
||||
>
|
||||
{copied ? "Copied!" : "Share"}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
@ -45,7 +45,9 @@ setup().then(() => {
|
|||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/collection" component={Collection} />
|
||||
<Route path="/store" component={Store} />
|
||||
<Route path="/profile" component={Profile} />
|
||||
<Route path="/profile/:username?">
|
||||
{(params) => <Profile username={params.username} />}
|
||||
</Route>
|
||||
</Switch>
|
||||
</AnimatePresence>
|
||||
</Shell>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import { useAtom } from "jotai";
|
|||
import { gameIdAtom, loginTokenAtom } from "../../atoms";
|
||||
import { Button } from "../../components/Button";
|
||||
import LeaderboardButton from "../../components/LeaderboardButton";
|
||||
import { ShareButton } from "../../components/ShareButton";
|
||||
import { Fragment, startTransition, Suspense, useEffect } from "react";
|
||||
import { Board } from "../../components/LazyBoard";
|
||||
import RegisterButton from "../../components/Auth/RegisterButton";
|
||||
import { Link } from "wouter";
|
||||
|
||||
interface EndlessProps {
|
||||
gameId?: string;
|
||||
|
|
@ -16,6 +18,7 @@ const Endless: React.FC<EndlessProps> = (props) => {
|
|||
const [loginToken] = useAtom(loginTokenAtom);
|
||||
const { data: game } = useWSQuery("game.getGameState", gameId!, !!gameId);
|
||||
const { data: settings } = useWSQuery("user.getSettings", null);
|
||||
const { data: currentUsername } = useWSQuery("user.getSelf", null);
|
||||
const startGame = useWSMutation("game.createGame");
|
||||
const { data: leaderboard } = useWSQuery("scoreboard.getScoreBoard", 100);
|
||||
const reveal = useWSMutation("game.reveal");
|
||||
|
|
@ -23,6 +26,9 @@ const Endless: React.FC<EndlessProps> = (props) => {
|
|||
const placeQuestionMark = useWSMutation("game.placeQuestionMark");
|
||||
const clearTile = useWSMutation("game.clearTile");
|
||||
|
||||
// Check if current user is spectating someone else's game
|
||||
const isSpectating = game && game.user !== currentUsername;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.gameId) {
|
||||
setGameId(props.gameId);
|
||||
|
|
@ -41,6 +47,18 @@ const Endless: React.FC<EndlessProps> = (props) => {
|
|||
Stage {game.stage}
|
||||
</div>
|
||||
<div className="grow" />
|
||||
{isSpectating && (
|
||||
<div className="flex items-center gap-2 text-white/80">
|
||||
<span>Spectating</span>
|
||||
<Link
|
||||
href={`/profile/${game.user}`}
|
||||
className="text-white/90 hover:text-purple-400 font-bold transition-colors"
|
||||
>
|
||||
{game.user}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<ShareButton gameId={gameId!} />
|
||||
<LeaderboardButton label="View Leaderboard" />
|
||||
</div>
|
||||
<Suspense>
|
||||
|
|
|
|||
|
|
@ -72,27 +72,35 @@ const TouchTooltip = ({
|
|||
);
|
||||
};
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
interface ProfileProps {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
const Profile: React.FC<ProfileProps> = ({ username: targetUsername }) => {
|
||||
const [availableWeeks, setAvailableWeeks] = useState(16); // Default to 4 months
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { data: username } = useWSQuery("user.getSelf", null);
|
||||
const { data: currentUsername } = useWSQuery("user.getSelf", null);
|
||||
|
||||
// Use targetUsername if provided, otherwise use current user's username
|
||||
const profileUsername = targetUsername || currentUsername;
|
||||
|
||||
const { data: heatmap } = useWSQuery(
|
||||
"user.getHeatmap",
|
||||
{ id: username! },
|
||||
!!username,
|
||||
{ id: profileUsername! },
|
||||
!!profileUsername,
|
||||
);
|
||||
const { data: profile } = useWSQuery(
|
||||
"user.getProfile",
|
||||
{ id: username! },
|
||||
!!username,
|
||||
{ id: profileUsername! },
|
||||
!!profileUsername,
|
||||
);
|
||||
const { data: pastGames } = useWSQuery(
|
||||
"game.getGames",
|
||||
{
|
||||
user: username!,
|
||||
user: profileUsername!,
|
||||
page: 0,
|
||||
},
|
||||
!!username,
|
||||
!!profileUsername,
|
||||
);
|
||||
const now = useMemo(() => dayjs(), []);
|
||||
const maxHeat = heatmap ? Math.max(...heatmap) : 0;
|
||||
|
|
@ -158,7 +166,7 @@ const Profile: React.FC = () => {
|
|||
<div className="grid md:[grid-template-columns:_2fr_3fr] gap-6 px-2 sm:px-0">
|
||||
<div className="mx-4 my-8 md:m-8 text-white flex flex-col sm:flex-row self-center">
|
||||
<div className="p-2 flex items-center text-2xl mb-2 sm:mb-0">
|
||||
{username}
|
||||
{profileUsername}
|
||||
</div>
|
||||
<div className="border-l-0 sm:border-l-white sm:border-l p-2 text-lg">
|
||||
<p>Total Games: {profile?.totalGames}</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue