From 1e008de6edf40d5a4084a72a0fa77b928fd4c87a Mon Sep 17 00:00:00 2001 From: Paul Gibby Date: Tue, 24 Feb 2026 08:17:16 -0500 Subject: [PATCH 1/3] Revert "Fix Join button theme" This reverts commit 4981fc2ddba296ca1df3937e793b91c3f3cae77b. --- src/routes/index/components/openGame/OpenGame.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/routes/index/components/openGame/OpenGame.tsx b/src/routes/index/components/openGame/OpenGame.tsx index 8c737c14c..bf6717532 100644 --- a/src/routes/index/components/openGame/OpenGame.tsx +++ b/src/routes/index/components/openGame/OpenGame.tsx @@ -33,7 +33,7 @@ const OpenGame = ({ } }; - const buttonClass = classNames(styles.button, 'secondary'); + const buttonClass = classNames(styles.button, hasDeckReady ? 'primary' : 'secondary'); return (
{entry.formatName}
}
- Join - +
); From 91b5d69276d3a46dcb70e09b30b446981cbe17d1 Mon Sep 17 00:00:00 2001 From: Paul Gibby Date: Tue, 24 Feb 2026 08:17:22 -0500 Subject: [PATCH 2/3] Revert "Update QuickJoinPanel.module.css" This reverts commit 2e73390a8f1e935c7cb192f319e0433e4060b9bb. --- .../index/components/quickJoin/QuickJoinPanel.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/index/components/quickJoin/QuickJoinPanel.module.css b/src/routes/index/components/quickJoin/QuickJoinPanel.module.css index b84c2ee96..79597acf0 100644 --- a/src/routes/index/components/quickJoin/QuickJoinPanel.module.css +++ b/src/routes/index/components/quickJoin/QuickJoinPanel.module.css @@ -22,7 +22,7 @@ display: flex; flex-direction: column; gap: 0.3rem; - font-size: var(--font-size); + font-size: 0.9rem; color: var(--theme-text, #ededed); } @@ -42,7 +42,7 @@ display: flex; align-items: center; gap: 0.5rem; - font-size: var(--font-size); + font-size: 0.9rem; cursor: pointer; margin: 0; } From 1a6a4a645d8a6d3d562ef4ca0563d56eb829c1b8 Mon Sep 17 00:00:00 2001 From: Paul Gibby Date: Tue, 24 Feb 2026 08:17:27 -0500 Subject: [PATCH 3/3] Revert "QuickJoin from menu" This reverts commit a968ffc72468a5342bb266441213150e0a13bfe6. --- .../ImageSelect/ImageSelect.module.css | 2 +- src/routes/game/create/CreateGame.module.css | 7 - src/routes/game/create/CreateGame.tsx | 3 +- src/routes/index/Index.tsx | 29 ++- .../index/components/openGame/OpenGame.tsx | 37 ++-- .../components/quickJoin/QuickJoinContext.tsx | 185 ------------------ .../quickJoin/QuickJoinPanel.module.css | 85 -------- .../components/quickJoin/QuickJoinPanel.tsx | 111 ----------- .../index/components/quickJoin/index.ts | 2 - 9 files changed, 27 insertions(+), 434 deletions(-) delete mode 100644 src/routes/index/components/quickJoin/QuickJoinContext.tsx delete mode 100644 src/routes/index/components/quickJoin/QuickJoinPanel.module.css delete mode 100644 src/routes/index/components/quickJoin/QuickJoinPanel.tsx delete mode 100644 src/routes/index/components/quickJoin/index.ts diff --git a/src/components/ImageSelect/ImageSelect.module.css b/src/components/ImageSelect/ImageSelect.module.css index 58b0a6034..9d0765bcd 100644 --- a/src/components/ImageSelect/ImageSelect.module.css +++ b/src/components/ImageSelect/ImageSelect.module.css @@ -15,7 +15,7 @@ cursor: pointer; min-height: 2.5rem; transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s; - margin-bottom: 0; + margin-bottom: var(--spacing); } .selectTrigger:hover { diff --git a/src/routes/game/create/CreateGame.module.css b/src/routes/game/create/CreateGame.module.css index e2dd6e052..eff06748d 100644 --- a/src/routes/game/create/CreateGame.module.css +++ b/src/routes/game/create/CreateGame.module.css @@ -15,13 +15,6 @@ } } -.title { - margin: 0 0 0.25rem; - font-size: 1.1rem; - font-weight: 700; - color: var(--theme-text, #ededed); -} - .fieldError { color: red; } diff --git a/src/routes/game/create/CreateGame.tsx b/src/routes/game/create/CreateGame.tsx index 7a6e470bd..cb511b8ad 100644 --- a/src/routes/game/create/CreateGame.tsx +++ b/src/routes/game/create/CreateGame.tsx @@ -373,7 +373,7 @@ const CreateGame = () => { return (
-

Create New Game

+

Create New Game

{/*

Warning - SOON! an update will be pushed to the live servers. The games in progress will crash and new games will be required.

*/} @@ -450,7 +450,6 @@ const CreateGame = () => { type="text" id="fabdb" aria-label="Deck Link - URL from FaBrary.net" - placeholder="https://fabrary.net/decks/…" {...register('fabdb')} aria-invalid={errors.deck?.message ? 'true' : undefined} /> diff --git a/src/routes/index/Index.tsx b/src/routes/index/Index.tsx index 96e7b70cc..199713d27 100644 --- a/src/routes/index/Index.tsx +++ b/src/routes/index/Index.tsx @@ -10,8 +10,6 @@ import News from 'routes/news'; import DevTool from './components/devTool'; import AboutSection from './components/AboutSection'; import CommunityContent from './components/CommunityContent'; -import { QuickJoinProvider } from './components/quickJoin/QuickJoinContext'; -import QuickJoinPanel from './components/quickJoin/QuickJoinPanel'; const Index = () => { usePageTitle('Home'); @@ -28,22 +26,19 @@ const Index = () => { return (
- -
- {import.meta.env.DEV && } -
- -
-
- - -
-
- - -
+
+ {import.meta.env.DEV && } +
+
- +
+ +
+
+ + +
+
diff --git a/src/routes/index/components/openGame/OpenGame.tsx b/src/routes/index/components/openGame/OpenGame.tsx index bf6717532..3b5302b24 100644 --- a/src/routes/index/components/openGame/OpenGame.tsx +++ b/src/routes/index/components/openGame/OpenGame.tsx @@ -1,11 +1,9 @@ -import React, { useContext } from 'react'; import classNames from 'classnames'; import { useNavigate } from 'react-router-dom'; import { IOpenGame } from '../gameList/GameList'; import styles from './OpenGame.module.css'; import { generateCroppedImageUrl } from '../../../../utils/cropImages'; import FriendBadge from '../gameList/FriendBadge'; -import QuickJoinContext from '../quickJoin/QuickJoinContext'; const OpenGame = ({ ix, @@ -19,27 +17,16 @@ const OpenGame = ({ isFriendsGame?: boolean; }) => { const navigate = useNavigate(); - const quickJoinCtx = useContext(QuickJoinContext); - - const hasDeckReady = !!(quickJoinCtx?.hasDeckConfigured); - - const handleJoin = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (hasDeckReady && quickJoinCtx) { - quickJoinCtx.quickJoin(entry.gameName); - } else { - navigate(`/game/join/${entry.gameName}`); - } - }; - - const buttonClass = classNames(styles.button, hasDeckReady ? 'primary' : 'secondary'); + const buttonClass = classNames(styles.button, 'secondary'); return (
{ + e.preventDefault(); + navigate(`/game/join/${entry.gameName}`); + }} >
{!!entry.p1Hero ? ( @@ -52,15 +39,17 @@ const OpenGame = ({ {isOther &&
{entry.formatName}
}
- +
); diff --git a/src/routes/index/components/quickJoin/QuickJoinContext.tsx b/src/routes/index/components/quickJoin/QuickJoinContext.tsx deleted file mode 100644 index 3fd2250bb..000000000 --- a/src/routes/index/components/quickJoin/QuickJoinContext.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useState -} from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useAppDispatch } from 'app/Hooks'; -import { setGameStart } from 'features/game/GameSlice'; -import { useJoinGameMutation, useGetFavoriteDecksQuery } from 'features/api/apiSlice'; -import { generateCroppedImageUrl } from 'utils/cropImages'; -import { ImageSelectOption } from 'components/ImageSelect'; -import { getReadableFormatName } from 'utils/formatUtils'; - -const shortenFormat = (format: string): string => { - if (!format) return ''; - if (format.toLowerCase() === 'classic constructed') return 'CC'; - const readable = getReadableFormatName(format); - return readable || format; -}; - -interface QuickJoinContextType { - selectedFavoriteDeck: string; - importDeckUrl: string; - saveDeck: boolean; - detectedFormat: string | null; - error: string | null; - isJoining: boolean; - hasDeckConfigured: boolean; - favoriteDeckOptions: ImageSelectOption[]; - isFavoritesLoading: boolean; - setSelectedFavoriteDeck: (v: string) => void; - setImportDeckUrl: (v: string) => void; - setSaveDeck: (v: boolean) => void; - setError: (v: string | null) => void; - quickJoin: (gameName: number) => Promise; -} - -const QuickJoinContext = createContext(null); - -const LS_FAVE_DECK_KEY = 'quickJoin_favoriteDeck'; -const LS_IMPORT_URL_KEY = 'quickJoin_importUrl'; -const LS_SAVE_DECK_KEY = 'quickJoin_saveDeck'; - -export const QuickJoinProvider = ({ children }: { children: React.ReactNode }) => { - const navigate = useNavigate(); - const dispatch = useAppDispatch(); - const [joinGame] = useJoinGameMutation(); - const { data: favoritesData, isLoading: isFavoritesLoading } = useGetFavoriteDecksQuery(undefined); - - const [selectedFavoriteDeck, setSelectedFavoriteDeckState] = useState( - () => localStorage.getItem(LS_FAVE_DECK_KEY) ?? '' - ); - const [importDeckUrl, setImportDeckUrlState] = useState( - () => localStorage.getItem(LS_IMPORT_URL_KEY) ?? '' - ); - const [saveDeck, setSaveDeckState] = useState( - () => localStorage.getItem(LS_SAVE_DECK_KEY) === 'true' - ); - const [detectedFormat, setDetectedFormat] = useState(null); - const [error, setError] = useState(null); - const [isJoining, setIsJoining] = useState(false); - - const favoriteDeckOptions: ImageSelectOption[] = useMemo(() => { - if (!favoritesData?.favoriteDecks) return []; - return favoritesData.favoriteDecks.map((deck) => ({ - value: deck.key, - label: `${deck.name}${deck.format ? ` (${shortenFormat(deck.format)})` : ''}`, - imageUrl: generateCroppedImageUrl(deck.hero) - })); - }, [favoritesData?.favoriteDecks]); - - useEffect(() => { - if (!selectedFavoriteDeck || !favoritesData?.favoriteDecks) { - if (!importDeckUrl) setDetectedFormat(null); - return; - } - const found = favoritesData.favoriteDecks.find( - (deck) => deck.key === selectedFavoriteDeck - ); - if (found?.format) { - setDetectedFormat(getReadableFormatName(found.format)); - } else { - setDetectedFormat(null); - } - }, [selectedFavoriteDeck, favoritesData?.favoriteDecks, importDeckUrl]); - - const setSelectedFavoriteDeck = useCallback((v: string) => { - setSelectedFavoriteDeckState(v); - localStorage.setItem(LS_FAVE_DECK_KEY, v); - if (v) { - setImportDeckUrlState(''); - localStorage.setItem(LS_IMPORT_URL_KEY, ''); - } - setError(null); - }, []); - - const setImportDeckUrl = useCallback((v: string) => { - setImportDeckUrlState(v); - localStorage.setItem(LS_IMPORT_URL_KEY, v); - if (v) { - setSelectedFavoriteDeckState(''); - localStorage.setItem(LS_FAVE_DECK_KEY, ''); - setDetectedFormat(null); - } - setError(null); - }, []); - - const setSaveDeck = useCallback((v: boolean) => { - setSaveDeckState(v); - localStorage.setItem(LS_SAVE_DECK_KEY, String(v)); - }, []); - - const hasDeckConfigured = !!(selectedFavoriteDeck || importDeckUrl.trim()); - - const quickJoin = useCallback( - async (gameName: number) => { - setError(null); - setIsJoining(true); - try { - const response = await joinGame({ - gameName, - playerID: 2, - favoriteDecks: selectedFavoriteDeck || undefined, - fabdb: importDeckUrl.trim() || undefined, - favoriteDeck: saveDeck - }).unwrap(); - - if (response.error) { - throw response.error; - } - - dispatch( - setGameStart({ - playerID: response.playerID ?? 0, - gameID: response.gameName ?? 0, - authKey: response.authKey ?? '' - }) - ); - navigate(`/game/lobby/${response.gameName}`, { - state: { playerID: response.playerID ?? 0 } - }); - } catch (err: any) { - const message = typeof err === 'string' ? err : err?.message ?? String(err); - setError(message); - } finally { - setIsJoining(false); - } - }, - [joinGame, selectedFavoriteDeck, importDeckUrl, saveDeck, dispatch, navigate] - ); - - const value: QuickJoinContextType = { - selectedFavoriteDeck, - importDeckUrl, - saveDeck, - detectedFormat, - error, - isJoining, - hasDeckConfigured, - favoriteDeckOptions, - isFavoritesLoading, - setSelectedFavoriteDeck, - setImportDeckUrl, - setSaveDeck, - setError, - quickJoin - }; - - return ( - - {children} - - ); -}; - -export const useQuickJoin = (): QuickJoinContextType => { - const ctx = useContext(QuickJoinContext); - if (!ctx) throw new Error('useQuickJoin must be used inside '); - return ctx; -}; - -export default QuickJoinContext; diff --git a/src/routes/index/components/quickJoin/QuickJoinPanel.module.css b/src/routes/index/components/quickJoin/QuickJoinPanel.module.css deleted file mode 100644 index 79597acf0..000000000 --- a/src/routes/index/components/quickJoin/QuickJoinPanel.module.css +++ /dev/null @@ -1,85 +0,0 @@ -.panel { - max-width: 900px; - border: 1px solid var(--theme-border, rgba(255, 255, 255, 0.2)); - border-radius: 10px; - margin: 1.5em auto; - margin-bottom: 0; - padding: 1.25rem 1.5rem 1rem; - background: var(--theme-section-background, linear-gradient(135deg, rgba(30, 35, 41, 0.8) 0%, rgba(30, 35, 41, 0.85) 100%)); - display: flex; - flex-direction: column; - gap: 0.75rem; -} - -.title { - margin: 0 0 0.25rem; - font-size: 1.1rem; - font-weight: 700; - color: var(--theme-text, #ededed); -} - -.label { - display: flex; - flex-direction: column; - gap: 0.3rem; - font-size: 0.9rem; - color: var(--theme-text, #ededed); -} - -.labelText { - display: flex; - align-items: center; - gap: 4px; -} - -.textInput { - width: 100%; - box-sizing: border-box; - margin-bottom: 0 !important; -} - -.toggleLabel { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.9rem; - cursor: pointer; - margin: 0; -} - -/* Error box mirrors the look of existing fieldError patterns */ -.errorBox { - display: flex; - align-items: flex-start; - gap: 0.4rem; - color: var(--alarm, red); - font-size: 0.875rem; - background: rgba(200, 0, 0, 0.08); - border: 1px solid rgba(200, 0, 0, 0.25); - border-radius: 6px; - padding: 0.5rem 0.75rem; -} - -.errorIcon { - flex-shrink: 0; - margin-top: 2px; -} - -.joiningText { - font-size: 0.875rem; - color: var(--theme-primary, #d4af37); - margin: 0; - text-align: center; -} - -.hint { - font-size: 0.8rem; - margin: 0; -} - -@media (max-width: 768px) { - .panel { - border-radius: 10px; - margin: 1em 0; - } -} diff --git a/src/routes/index/components/quickJoin/QuickJoinPanel.tsx b/src/routes/index/components/quickJoin/QuickJoinPanel.tsx deleted file mode 100644 index fb3926161..000000000 --- a/src/routes/index/components/quickJoin/QuickJoinPanel.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; -import { FaExclamationCircle, FaQuestionCircle } from 'react-icons/fa'; -import { ImageSelect } from 'components/ImageSelect'; -import useAuth from 'hooks/useAuth'; -import { useQuickJoin } from './QuickJoinContext'; -import styles from './QuickJoinPanel.module.css'; - -const QuickJoinPanel = () => { - const { isLoggedIn } = useAuth(); - const { - selectedFavoriteDeck, - importDeckUrl, - saveDeck, - detectedFormat, - error, - isJoining, - favoriteDeckOptions, - isFavoritesLoading, - setSelectedFavoriteDeck, - setImportDeckUrl, - setSaveDeck, - setError - } = useQuickJoin(); - - if (!isLoggedIn) return null; - - return ( -
-

Use Deck to Join

- - - - - - - - {detectedFormat && ( - - )} -

- {selectedFavoriteDeck || importDeckUrl.trim() ? ( - <>Click Join on any open game to join instantly. - ) : ( - <>Select or import a deck above, then click Join on any open game. - )} -

- - {error && ( -
- {error} -
- )} - - {isJoining && ( -

- Joining game… -

- )} - - -
- ); -}; - -export default QuickJoinPanel; diff --git a/src/routes/index/components/quickJoin/index.ts b/src/routes/index/components/quickJoin/index.ts deleted file mode 100644 index 5aff5c82d..000000000 --- a/src/routes/index/components/quickJoin/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { QuickJoinProvider, useQuickJoin } from './QuickJoinContext'; -export { default as QuickJoinPanel } from './QuickJoinPanel';