mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
new socials buttons and stuffses
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
import { useSession } from "../context/SessionContext";
|
||||
import { FaDiscord } from "react-icons/fa";
|
||||
|
||||
export default function DiscordInviteButton({ text = "Join our Discord!", className = "" }) {
|
||||
const { session } = useSession();
|
||||
const discordInvite = session?.discord?.invite || null;
|
||||
|
||||
if (!discordInvite) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={discordInvite}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white rainbow-animate-bg transition justify-center gap-1 rounded-md ${className}`}
|
||||
// animated rainbow backgound
|
||||
// className="inline-flex items-center px-3 py-2 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white rounded hover:from-indigo-600 hover:via-purple-600 hover:to-pink-600 transition"
|
||||
>
|
||||
<FaDiscord className="mr-0" />
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useSession } from "../context/SessionContext";
|
||||
import { FaCoffee } from "react-icons/fa";
|
||||
|
||||
export default function KoFiButton({ text = "Support me on Ko-fi!", className = "" }) {
|
||||
const { session } = useSession();
|
||||
const kofiLink = session?.kofi?.link || null;
|
||||
|
||||
if (!kofiLink) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={kofiLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white kofi-animate-bg transition justify-center gap-1 rounded-md ${className}`}
|
||||
>
|
||||
<FaCoffee className="mr-0" />
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import AuthPanel from './AuthPanel.jsx';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import DiscordInviteButton from './DiscordInviteButton.jsx';
|
||||
import SocialButton from './SocialButton.jsx';
|
||||
import ChatPanel from './ChatPanel.jsx';
|
||||
import NicknameForm from './NicknameForm.jsx';
|
||||
|
||||
@@ -31,6 +31,13 @@ export default function ModeGateOverlay() {
|
||||
const reason = session?.adminReason?.text || '';
|
||||
const reasonUpdatedAt = session?.adminReason?.updatedAt || null;
|
||||
const timezone = session?.timezone || 'UTC';
|
||||
const discordUrl =
|
||||
session?.socials?.find((entry) => {
|
||||
const key = String(entry?.id || entry?.label || '').toLowerCase();
|
||||
return key === 'discord';
|
||||
})?.url ||
|
||||
session?.discord?.invite ||
|
||||
null;
|
||||
const restricted = RESTRICTED_MODES.has(mode);
|
||||
const privileged = mode === 'lockdown' ? LOCKDOWN_ROLES.has(role) : PRIVILEGED_ROLES.has(role);
|
||||
const [now, setNow] = useState(() => new Date());
|
||||
@@ -81,8 +88,12 @@ export default function ModeGateOverlay() {
|
||||
<div className="surface-muted">
|
||||
<AuthPanel />
|
||||
</div>
|
||||
<div className='w-full justify-center items-center'>
|
||||
<DiscordInviteButton text='Join our Discord server for updates!'/>
|
||||
<div className="w-full justify-center items-center">
|
||||
<SocialButton
|
||||
id="discord"
|
||||
label="Join our Discord server for updates!"
|
||||
url={discordUrl}
|
||||
/>
|
||||
</div>
|
||||
You can still use the chat while the server is locked:
|
||||
{/* set max height of this box */}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../settings/index.js';
|
||||
import { useSocket } from '../context/SocketContext.jsx';
|
||||
import NicknameForm from './NicknameForm.jsx';
|
||||
import DiscordInviteButton from './DiscordInviteButton.jsx';
|
||||
import KoFiButton from './KoFiButton.jsx';
|
||||
import SocialButtonsGrid from './SocialButtonsGrid.jsx';
|
||||
|
||||
function roleColors(role) {
|
||||
switch (role) {
|
||||
@@ -97,10 +96,7 @@ export default function RawUserPilePanel({
|
||||
<NicknameForm compact={compact} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-0.5 sm:grid-cols-2 md:grid-cols-1">
|
||||
<DiscordInviteButton />
|
||||
<KoFiButton />
|
||||
</div>
|
||||
<SocialButtonsGrid />
|
||||
</div>
|
||||
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { FaBook, FaCoffee, FaCrown, FaDiscord, FaLink } from 'react-icons/fa';
|
||||
|
||||
const ICONS_BY_ID = {
|
||||
discord: FaDiscord,
|
||||
kofi: FaCoffee,
|
||||
'ko-fi': FaCoffee,
|
||||
wiki: FaBook,
|
||||
throne: FaCrown,
|
||||
};
|
||||
|
||||
const VARIANT_BY_ID = {
|
||||
discord: 'rainbow-animate-bg',
|
||||
kofi: 'kofi-animate-bg',
|
||||
'ko-fi': 'kofi-animate-bg',
|
||||
wiki: 'wiki-animate-bg',
|
||||
throne: 'throne-animate-bg',
|
||||
};
|
||||
|
||||
function normalizeId(id, label) {
|
||||
if (typeof id === 'string' && id.trim()) return id.trim().toLowerCase();
|
||||
if (typeof label === 'string' && label.trim()) {
|
||||
return label
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function SocialButton({ id, label, url, className = '' }) {
|
||||
if (!url) return null;
|
||||
const key = normalizeId(id, label);
|
||||
const Icon = ICONS_BY_ID[key] || FaLink;
|
||||
const variantClass = VARIANT_BY_ID[key] || 'bg-slate-700 hover:bg-slate-600';
|
||||
const text = label || id || 'Link';
|
||||
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={text}
|
||||
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white transition justify-center gap-1 rounded-md ${variantClass} ${className}`}
|
||||
>
|
||||
<Icon className="mr-0" />
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import SocialButton from './SocialButton.jsx';
|
||||
|
||||
function normalizeSocials(session) {
|
||||
const configured = Array.isArray(session?.socials) ? session.socials : null;
|
||||
if (configured && configured.length) {
|
||||
return configured;
|
||||
}
|
||||
const fallback = [];
|
||||
if (session?.discord?.invite) {
|
||||
fallback.push({ id: 'discord', label: 'Discord', url: session.discord.invite });
|
||||
}
|
||||
if (session?.kofi?.link) {
|
||||
fallback.push({ id: 'kofi', label: 'Ko-fi', url: session.kofi.link });
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function normalizeEntry(entry) {
|
||||
if (!entry || typeof entry !== 'object') return null;
|
||||
const id = entry.id || entry.service || entry.key || entry.name || null;
|
||||
const label = entry.label || entry.title || entry.name || id || 'Link';
|
||||
const url = entry.url || entry.link || entry.href || null;
|
||||
return url ? { id, label, url } : null;
|
||||
}
|
||||
|
||||
export default function SocialButtonsGrid({ className = '' }) {
|
||||
const { session } = useSession();
|
||||
const socials = normalizeSocials(session)
|
||||
.map(normalizeEntry)
|
||||
.filter(Boolean)
|
||||
.slice(0, 4);
|
||||
|
||||
if (!socials.length) return null;
|
||||
|
||||
return (
|
||||
<div className={`grid grid-cols-2 grid-rows-2 gap-0.5 ${className}`}>
|
||||
{socials.map((entry) => (
|
||||
<SocialButton key={`${entry.id || entry.label}-${entry.url}`} {...entry} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,8 +3,7 @@ import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../settings/index.js';
|
||||
import { useSocket } from '../context/SocketContext.jsx';
|
||||
import NicknameForm from './NicknameForm.jsx';
|
||||
import DiscordInviteButton from './DiscordInviteButton.jsx';
|
||||
import KoFiButton from './KoFiButton.jsx';
|
||||
import SocialButtonsGrid from './SocialButtonsGrid.jsx';
|
||||
|
||||
export function NicknameEntryPanel({ compact = false }) {
|
||||
return (
|
||||
@@ -19,10 +18,7 @@ export function NicknameEntryPanel({ compact = false }) {
|
||||
export function LinkButtonsPanel() {
|
||||
return (
|
||||
<section className="panel-section flex h-full min-h-0 flex-col gap-0.5 text-base">
|
||||
<div className="grid flex-1 min-h-0 gap-0.5 grid-rows-2">
|
||||
<DiscordInviteButton className="h-full" />
|
||||
<KoFiButton className="h-full" />
|
||||
</div>
|
||||
<SocialButtonsGrid className="flex-1 min-h-0" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -185,10 +181,7 @@ export default function UserListPanel({
|
||||
<NicknameForm compact={compact} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-0.5 sm:grid-cols-2 md:grid-cols-1">
|
||||
<DiscordInviteButton />
|
||||
<KoFiButton />
|
||||
</div>
|
||||
<SocialButtonsGrid />
|
||||
</div>
|
||||
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
|
||||
import { useChat } from '../context/ChatContext.jsx';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../settings/index.js';
|
||||
import DiscordInviteButton from './DiscordInviteButton.jsx';
|
||||
import SocialButton from './SocialButton.jsx';
|
||||
|
||||
const RESTART_DELAY_MS = 2000;
|
||||
const UNMUTE_RETRY_MS = 3000;
|
||||
@@ -61,6 +61,7 @@ export default function VideoTile({
|
||||
isActiveDriver = false,
|
||||
idleSkipSeconds = null,
|
||||
}) {
|
||||
const { session } = useSession();
|
||||
const videoRef = useRef(null);
|
||||
const audioRef = useRef(null);
|
||||
const restartTimer = useRef(null);
|
||||
@@ -110,6 +111,13 @@ export default function VideoTile({
|
||||
const overlayMotors = overcurrentMotors.length ? overcurrentMotors : limiterActive ? ['limiter'] : [];
|
||||
const overlayFill = limiterFill ?? (overcurrentMotors.length ? 1 : 0);
|
||||
const overlayVisible = Boolean(overlayMotors.length);
|
||||
const discordUrl =
|
||||
session?.socials?.find((entry) => {
|
||||
const key = String(entry?.id || entry?.label || '').toLowerCase();
|
||||
return key === 'discord';
|
||||
})?.url ||
|
||||
session?.discord?.invite ||
|
||||
null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!debugHud) return;
|
||||
@@ -471,7 +479,11 @@ export default function VideoTile({
|
||||
>
|
||||
<div className="text-center">{qualityNotice}</div>
|
||||
<div className="pointer-events-auto mt-0">
|
||||
<DiscordInviteButton text={'Join our Discord server while you wait!'} />
|
||||
<SocialButton
|
||||
id="discord"
|
||||
label="Join our Discord server while you wait!"
|
||||
url={discordUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +127,30 @@ body {
|
||||
background-size: 800% 800%;
|
||||
animation: rainbowBG 240s ease infinite;
|
||||
}
|
||||
|
||||
.wiki-animate-bg {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
#14b8a6,
|
||||
#38bdf8,
|
||||
#22d3ee,
|
||||
#14b8a6
|
||||
);
|
||||
background-size: 700% 700%;
|
||||
animation: rainbowBG 240s ease infinite;
|
||||
}
|
||||
|
||||
.throne-animate-bg {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
#f59e0b,
|
||||
#fbbf24,
|
||||
#fde68a,
|
||||
#f59e0b
|
||||
);
|
||||
background-size: 700% 700%;
|
||||
animation: rainbowBG 240s ease infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
Reference in New Issue
Block a user