diff --git a/i18n/en_US.json b/i18n/en_US.json index ce82df1..7908615 100644 --- a/i18n/en_US.json +++ b/i18n/en_US.json @@ -141,5 +141,6 @@ "TERMS_OF_SERVICE": "Terms of Service", "TOS_HINT": "Masterbase features are unavailable until you agree to the terms of service.", "AGREE_TO_TOS": "Agree to TOS", - "GET_ONE_HERE": "get one here" + "GET_ONE_HERE": "get one here", + "CONFIRM_EXTERNAL_LINKS": "Confirm using links to external sites" } diff --git a/src/Pages/Preferences/Preferences.tsx b/src/Pages/Preferences/Preferences.tsx index 893e659..0a4a69a 100644 --- a/src/Pages/Preferences/Preferences.tsx +++ b/src/Pages/Preferences/Preferences.tsx @@ -155,6 +155,17 @@ const Preferences = () => { } /> + +
+ {t('CONFIRM_EXTERNAL_LINKS')} +
+ + handleSettingChange('confirmExternalLinks', e, 'external') + } + /> +
{t('PREF_COLORS_PRECEDENCE')} diff --git a/src/api/api.d.ts b/src/api/api.d.ts index 74b768b..fdbba1f 100644 --- a/src/api/api.d.ts +++ b/src/api/api.d.ts @@ -2,6 +2,7 @@ interface Settings { external: { language?: string; openInApp?: boolean; + confirmExternalLinks?: boolean; colors?: { You: string; Player: string; diff --git a/src/components/General/ContextMenu/ContextMenu.tsx b/src/components/General/ContextMenu/ContextMenu.tsx index cc3582a..a0765b4 100644 --- a/src/components/General/ContextMenu/ContextMenu.tsx +++ b/src/components/General/ContextMenu/ContextMenu.tsx @@ -1,4 +1,11 @@ -import { CSSProperties, useContext, useEffect, useRef, useState } from 'react'; +import { + CSSProperties, + MouseEvent, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import './ContextMenu.css'; import { ContextMenuContext } from '@context'; @@ -18,7 +25,8 @@ const ContextMenuContent = () => { left: position.x, }; - const onItemClick = (itemAction: () => void) => { + const onItemClick = (e: MouseEvent, itemAction: () => void) => { + e.stopPropagation(); hideMenu(); itemAction(); }; @@ -88,8 +96,8 @@ const ContextMenuContent = () => {
{ - if (item?.onClick) onItemClick(item?.onClick); + onClick={(e) => { + if (item?.onClick) onItemClick(e, item?.onClick); }} onMouseEnter={() => onMouseEnter(index)} onMouseLeave={() => onMouseLeave(index)} @@ -115,8 +123,8 @@ const ContextMenuContent = () => {
{ - if (menuItem?.onClick) onItemClick(menuItem?.onClick); + onClick={(e) => { + if (menuItem?.onClick) onItemClick(e, menuItem?.onClick); }} > {menuItem.label} diff --git a/src/components/TF2/Player/Modals/ConfirmNavigationModal.tsx b/src/components/TF2/Player/Modals/ConfirmNavigationModal.tsx new file mode 100644 index 0000000..774e15e --- /dev/null +++ b/src/components/TF2/Player/Modals/ConfirmNavigationModal.tsx @@ -0,0 +1,66 @@ +import { Button, Checkbox } from '@components/General'; +import { useModal } from '@context'; +import React from 'react'; + +interface ConfirmNavigationModalProps { + link: string; + onConfirm?: () => void; + onCancel?: () => void; + onDontShowAgain?: () => void; +} + +const ConfirmNavigationModal = ({ + link, + onConfirm, + onCancel, + onDontShowAgain, +}: ConfirmNavigationModalProps) => { + const [dontShowAgain, setDontShowAgain] = React.useState(false); + + const { closeModal } = useModal(); + + const handleAction = () => { + if (dontShowAgain) { + onDontShowAgain?.(); + } + closeModal(); + }; + + return ( +
+
+ You are about to open a link to an external site:{' '} + {new URL(link).hostname}. +
+ Are you sure you want to continue? +
+
+ Don't show this again + { + setDontShowAgain(checked); + }} + /> + + +
+
+ ); +}; + +export default ConfirmNavigationModal; diff --git a/src/components/TF2/Player/Player.tsx b/src/components/TF2/Player/Player.tsx index 4a1bf76..217829a 100644 --- a/src/components/TF2/Player/Player.tsx +++ b/src/components/TF2/Player/Player.tsx @@ -25,25 +25,28 @@ import { convertSteamID64toSteamID2, convertSteamID64toSteamID3, } from '@api/steamid'; +import { profileLinks } from '../../../constants/playerConstants'; +import ConfirmNavigationModal from './Modals/ConfirmNavigationModal'; +import { setSettingKey } from '@api/preferences'; interface PlayerProps { player: PlayerInfo; icon?: string; className?: string; onImageLoad?: () => void; - playerColors?: Record; - openInApp?: boolean; userSteamID?: string; cheatersInLobby: PlayerInfo[]; + settings: Settings['external']; + setSettings: React.Dispatch>; } const Player = ({ player, className, onImageLoad, - playerColors, - openInApp, cheatersInLobby, + settings, + setSettings, }: PlayerProps) => { const isFirstRefresh = useRef(true); // Context Menu @@ -57,7 +60,7 @@ const Player = ({ const [pfp, setPfp] = useState('./person.webp'); const [showPlayerDetails, setShowPlayerDetails] = useState(false); - const urlToOpen = openInApp + const urlToOpen = settings.openInApp ? `steam://url/SteamIDPage/${player.steamID64}` : `https://steamcommunity.com/profiles/${player.steamID64}`; @@ -72,12 +75,12 @@ const Player = ({ // const color = displayColor(playerColors!, player, cheatersInLobby); const [color, setColor] = useState( - displayColor(playerColors!, player, cheatersInLobby), + displayColor(settings.colors!, player, cheatersInLobby), ); useEffect(() => { - setColor(displayColor(playerColors!, player, cheatersInLobby)); - }, [player.localVerdict, playerColors, player, cheatersInLobby]); + setColor(displayColor(settings.colors!, player, cheatersInLobby)); + }, [player.localVerdict, settings.colors, player, cheatersInLobby]); const localizedLocalVerdictOptions = makeLocalizedVerdictOptions(); @@ -129,11 +132,45 @@ const Player = ({ }); }, [player.steamInfo?.pfp]); + const formatUrl = (url: string) => { + url = url.replace('{{ID64}}', player.steamID64); + return url; + }; + const handleContextMenu = (event: MouseEvent) => { event.preventDefault(); const menuItems: MenuItem[] = [ { label: 'Open Profile', + multiOptions: [ + { + label: 'Steam Profile', + onClick: () => parent.open(urlToOpen, '_blank'), + }, + ...profileLinks.map(([name, url]) => ({ + label: name, + onClick: () => { + if (settings.confirmExternalLinks ?? true) { + openModal( + parent.open(formatUrl(url), '_blank')} + onDontShowAgain={() => { + setSettingKey('confirmExternalLinks', false, 'external'); + setSettings((prev) => ({ + ...prev, + confirmExternalLinks: false, + })); + }} + />, + { dismissable: true }, + ); + } else { + parent.open(formatUrl(url), '_blank'); + } + }, + })), + ], onClick: () => { parent.open(urlToOpen, '_blank'); }, @@ -233,7 +270,7 @@ const Player = ({ // Causes new info to immediately show player.localVerdict = e.toString(); updatePlayer(player.steamID64, e.toString()); - setColor(displayColor(playerColors!, player, cheatersInLobby)); + setColor(displayColor(settings.colors!, player, cheatersInLobby)); }} />
setShowPlayerDetails(!showPlayerDetails)}> diff --git a/src/components/TF2/ScoreboardTable/ScoreboardTable.tsx b/src/components/TF2/ScoreboardTable/ScoreboardTable.tsx index 578139d..695c73c 100644 --- a/src/components/TF2/ScoreboardTable/ScoreboardTable.tsx +++ b/src/components/TF2/ScoreboardTable/ScoreboardTable.tsx @@ -20,7 +20,7 @@ const ScoreboardTable = ({ }: ScoreboardTableProps) => { // Store the users playerID const [userSteamID, setUserSteamID] = useState('0'); - const [playerSettings, setPlayerSettings] = useState({ + const [settings, setSettings] = useState({ colors: { You: 'none', Player: 'none', @@ -33,18 +33,19 @@ const ScoreboardTable = ({ Bot: 'none', }, openInApp: false, + confirmExternalLinks: true, }); useEffect(() => { - const fetchTeamColors = async () => { + const fetchSettings = async () => { try { const { external } = await getAllSettings(); // Replace this with the actual async function that fetches colors - setPlayerSettings(external); + setSettings(external); } catch (error) { console.error('Error fetching team colors:', error); } }; - fetchTeamColors(); + fetchSettings(); }, []); useEffect(() => { @@ -108,12 +109,12 @@ const ScoreboardTable = ({ // Provide the Context Menu Provider to the Element ))} diff --git a/src/constants/playerConstants.ts b/src/constants/playerConstants.ts new file mode 100644 index 0000000..eb74c46 --- /dev/null +++ b/src/constants/playerConstants.ts @@ -0,0 +1,12 @@ +export const profileLinks = [ + ['Steamrep', 'https://steamrep.com/profiles/{{ID64}}'], + ['Backpack.tf', 'https://backpack.tf/profiles/{{ID64}}'], + ['SteamHistory', 'https://steamhistory.net/id/{{ID64}}'], + ['RGL', 'https://rgl.gg/Public/PlayerProfile?p={{ID64}}'], + ['Logs.tf', 'https://logs.tf/profile/{{ID64}}'], + ['Demos.tf', 'https://demos.tf/profiles/{{ID64}}'], + ['Trends.tf', 'https://trends.tf/player/{{ID64}}'], + ['ETF2L', 'https://etf2l.org/search/{{ID64}}'], + ['UGC', 'https://www.ugcleague.com/players_page.cfm?player_id={{ID64}}'], + ['Ozfortress', 'https://ozfortress.com/users/steam_id/{{ID64}}'], +];