diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index eab4bc92..48407b32 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -1,4 +1,7 @@ { + "app": { + "successfully_signed_in": "Successfully signed in" + }, "home": { "featured": "Featured", "trending": "Trending", @@ -243,6 +246,7 @@ "try_again": "Please, try again", "sign_out_modal_title": "Are you sure?", "cancel": "Cancel", + "successfully_signed_out": "Successfully signed out", "sign_out": "Sign out", "playing_for": "Playing for {{amount}}" } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 910d965c..739c573c 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -1,4 +1,7 @@ { + "app": { + "successfully_signed_in": "Logado com sucesso" + }, "home": { "featured": "Destaque", "trending": "Populares", @@ -242,6 +245,7 @@ "saved_successfully": "Salvo com sucesso", "try_again": "Por favor, tente novamente", "cancel": "Cancelar", + "successfully_signed_out": "Deslogado com sucesso", "sign_out": "Sair da conta", "sign_out_modal_title": "Tem certeza?", "playing_for": "Jogando por {{amount}}" diff --git a/src/main/events/auth/open-auth-window.ts b/src/main/events/auth/open-auth-window.ts new file mode 100644 index 00000000..e93a5a42 --- /dev/null +++ b/src/main/events/auth/open-auth-window.ts @@ -0,0 +1,7 @@ +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; + +const openAuthWindow = async (_event: Electron.IpcMainInvokeEvent) => + WindowManager.openAuthWindow(); + +registerEvent("openAuthWindow", openAuthWindow); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 548106e0..32c9243c 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -41,6 +41,7 @@ import "./download-sources/add-download-source"; import "./download-sources/remove-download-source"; import "./download-sources/sync-download-sources"; import "./auth/signout"; +import "./auth/open-auth-window"; import "./user/get-user"; import "./profile/get-me"; import "./profile/update-profile"; diff --git a/src/main/index.ts b/src/main/index.ts index 760b8283..b2d07275 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,7 +8,6 @@ import { DownloadManager, logger, WindowManager } from "@main/services"; import { dataSource } from "@main/data-source"; import * as resources from "@locales"; import { userPreferencesRepository } from "@main/repository"; -import { HydraApi } from "./services/hydra-api"; const { autoUpdater } = updater; @@ -74,7 +73,7 @@ app.on("browser-window-created", (_, window) => { optimizer.watchWindowShortcuts(window); }); -app.on("second-instance", (_event, commandLine) => { +app.on("second-instance", (_event) => { // Someone tried to run a second instance, we should focus our window. if (WindowManager.mainWindow) { if (WindowManager.mainWindow.isMinimized()) @@ -85,20 +84,16 @@ app.on("second-instance", (_event, commandLine) => { WindowManager.createMainWindow(); } - const [, path] = commandLine.pop()?.split("://") ?? []; - if (path) { - if (path.startsWith("auth")) { - HydraApi.handleExternalAuth(path); - } else { - WindowManager.redirect(path); - } - } + // const [, path] = commandLine.pop()?.split("://") ?? []; + // if (path) { + // WindowManager.redirect(path); + // } }); -app.on("open-url", (_event, url) => { - const [, path] = url.split("://"); - WindowManager.redirect(path); -}); +// app.on("open-url", (_event, url) => { +// const [, path] = url.split("://"); +// WindowManager.redirect(path); +// }); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 8e50e646..e6e042db 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -23,8 +23,8 @@ export class HydraApi { return this.userAuth.authToken !== ""; } - static async handleExternalAuth(auth: string) { - const { payload } = url.parse(auth, true).query; + static async handleExternalAuth(uri: string) { + const { payload } = url.parse(uri, true).query; const decodedBase64 = atob(payload as string); const jsonData = JSON.parse(decodedBase64); diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 7b57445b..3e3eaf1c 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -15,6 +15,7 @@ import icon from "@resources/icon.png?asset"; import trayIcon from "@resources/tray-icon.png?asset"; import { gameRepository, userPreferencesRepository } from "@main/repository"; import { IsNull, Not } from "typeorm"; +import { HydraApi } from "./hydra-api"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -80,6 +81,41 @@ export class WindowManager { }); } + public static openAuthWindow() { + if (this.mainWindow) { + const authWindow = new BrowserWindow({ + width: 600, + height: 640, + backgroundColor: "#1c1c1c", + parent: this.mainWindow, + modal: true, + show: false, + maximizable: false, + resizable: false, + minimizable: false, + webPreferences: { + sandbox: false, + }, + }); + + authWindow.removeMenu(); + + authWindow.loadURL("https://auth.hydra.losbroxas.org/"); + + authWindow.once("ready-to-show", () => { + authWindow.show(); + }); + + authWindow.webContents.on("will-navigate", (_event, url) => { + if (url.startsWith("hydralauncher://auth")) { + authWindow.close(); + + HydraApi.handleExternalAuth(url); + } + }); + } + } + public static redirect(hash: string) { if (!this.mainWindow) this.createMainWindow(); this.loadURL(hash); diff --git a/src/preload/index.ts b/src/preload/index.ts index c92343c5..493a3795 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -142,6 +142,7 @@ contextBridge.exposeInMainWorld("electron", { /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), + openAuthWindow: () => ipcRenderer.invoke("openAuthWindow"), onSignIn: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signin", listener); diff --git a/src/renderer/src/app.css.ts b/src/renderer/src/app.css.ts index 65264240..a5f9394b 100644 --- a/src/renderer/src/app.css.ts +++ b/src/renderer/src/app.css.ts @@ -111,6 +111,6 @@ export const titleBar = style({ alignItems: "center", padding: `0 ${SPACING_UNIT * 2}px`, WebkitAppRegion: "drag", - zIndex: "2", + zIndex: "4", borderBottom: `1px solid ${vars.color.border}`, } as ComplexStyleRule); diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 0c92eccf..d98f1e1f 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -7,6 +7,7 @@ import { useAppSelector, useDownload, useLibrary, + useToast, useUserDetails, } from "@renderer/hooks"; @@ -23,6 +24,7 @@ import { setProfileBackground, setGameRunning, } from "@renderer/features"; +import { useTranslation } from "react-i18next"; export interface AppProps { children: React.ReactNode; @@ -32,6 +34,8 @@ export function App() { const contentRef = useRef(null); const { updateLibrary, library } = useLibrary(); + const { t } = useTranslation("app"); + const { clearDownload, setLastPacket } = useDownload(); const { fetchUserDetails, updateUserDetails, clearUserDetails } = @@ -50,6 +54,8 @@ export function App() { const toast = useAppSelector((state) => state.toast); + const { showSuccessToast } = useToast(); + useEffect(() => { Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then( ([preferences]) => { @@ -96,6 +102,15 @@ export function App() { }); }, [dispatch, fetchUserDetails]); + const onSignIn = useCallback(() => { + fetchUserDetails().then((response) => { + if (response) { + updateUserDetails(response); + showSuccessToast(t("successfully_signed_in")); + } + }); + }, [fetchUserDetails, t, showSuccessToast, updateUserDetails]); + useEffect(() => { const unsubscribe = window.electron.onGamesRunning((gamesRunning) => { if (gamesRunning.length) { @@ -124,23 +139,17 @@ export function App() { useEffect(() => { const listeners = [ - window.electron.onSignIn(() => { - fetchUserDetails().then((response) => { - if (response) updateUserDetails(response); - }); - }), + window.electron.onSignIn(onSignIn), window.electron.onLibraryBatchComplete(() => { updateLibrary(); }), - window.electron.onSignOut(() => { - clearUserDetails(); - }), + window.electron.onSignOut(() => clearUserDetails()), ]; return () => { listeners.forEach((unsubscribe) => unsubscribe()); }; - }, [fetchUserDetails, updateUserDetails, clearUserDetails, updateLibrary]); + }, [onSignIn, updateLibrary, clearUserDetails]); const handleSearch = useCallback( (query: string) => { @@ -194,6 +203,13 @@ export function App() { )} + +
@@ -211,13 +227,6 @@ export function App() {
- - ); } diff --git a/src/renderer/src/components/backdrop/backdrop.css.ts b/src/renderer/src/components/backdrop/backdrop.css.ts index 1c95717e..1ccfe12f 100644 --- a/src/renderer/src/components/backdrop/backdrop.css.ts +++ b/src/renderer/src/components/backdrop/backdrop.css.ts @@ -1,7 +1,7 @@ import { keyframes } from "@vanilla-extract/css"; import { recipe } from "@vanilla-extract/recipes"; -import { SPACING_UNIT } from "../../theme.css"; +import { SPACING_UNIT, vars } from "../../theme.css"; export const backdropFadeIn = keyframes({ "0%": { backdropFilter: "blur(0px)", backgroundColor: "rgba(0, 0, 0, 0.5)" }, @@ -30,8 +30,8 @@ export const backdrop = recipe({ display: "flex", justifyContent: "center", alignItems: "center", - zIndex: 1, - top: 0, + zIndex: vars.zIndex.backdrop, + top: "0", padding: `${SPACING_UNIT * 3}px`, backdropFilter: "blur(2px)", transition: "all ease 0.2s", diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts index a1f5d1a8..e70f3556 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.css.ts +++ b/src/renderer/src/components/bottom-panel/bottom-panel.css.ts @@ -12,7 +12,7 @@ export const bottomPanel = style({ transition: "all ease 0.2s", justifyContent: "space-between", position: "relative", - zIndex: "1", + zIndex: vars.zIndex.bottomPanel, }); export const downloadsButton = style({ diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index ac1c9c96..914481b0 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -17,7 +17,7 @@ export function SidebarProfile() { const handleButtonClick = () => { if (userDetails === null) { - window.electron.openExternal("https://auth.hydra.losbroxas.org"); + window.electron.openAuthWindow(); return; } diff --git a/src/renderer/src/components/toast/toast.css.ts b/src/renderer/src/components/toast/toast.css.ts index 2f4e8d03..a07bb105 100644 --- a/src/renderer/src/components/toast/toast.css.ts +++ b/src/renderer/src/components/toast/toast.css.ts @@ -31,7 +31,7 @@ export const toast = recipe({ display: "flex", flexDirection: "column", justifyContent: "space-between", - zIndex: "0", + zIndex: vars.zIndex.toast, maxWidth: "500px", }, variants: { diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ad03f1ff..64f8d0a3 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -118,6 +118,7 @@ declare global { /* Auth */ signOut: () => Promise; + openAuthWindow: () => Promise; onSignIn: (cb: () => void) => () => Electron.IpcRenderer; onSignOut: (cb: () => void) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 3802b110..ad620868 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -6,7 +6,12 @@ import { SPACING_UNIT, vars } from "@renderer/theme.css"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; -import { useAppSelector, useDate, useUserDetails } from "@renderer/hooks"; +import { + useAppSelector, + useDate, + useToast, + useUserDetails, +} from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; import { buildGameDetailsPath, steamUrlBuilder } from "@renderer/helpers"; import { PersonIcon, TelescopeIcon } from "@primer/octicons-react"; @@ -28,6 +33,7 @@ export function UserContent({ const { t, i18n } = useTranslation("user_profile"); const { userDetails, profileBackground, signOut } = useUserDetails(); + const { showSuccessToast } = useToast(); const [showEditProfileModal, setShowEditProfileModal] = useState(false); const [showSignOutModal, setShowSignOutModal] = useState(false); @@ -70,7 +76,10 @@ export function UserContent({ }; const handleConfirmSignout = async () => { - signOut(); + await signOut(); + + showSuccessToast(t("successfully_signed_out")); + navigate("/"); }; diff --git a/src/renderer/src/theme.css.ts b/src/renderer/src/theme.css.ts index c748c1c7..6b520614 100644 --- a/src/renderer/src/theme.css.ts +++ b/src/renderer/src/theme.css.ts @@ -21,4 +21,10 @@ export const vars = createGlobalTheme(":root", { body: "14px", small: "12px", }, + zIndex: { + toast: "2", + bottomPanel: "3", + titleBar: "4", + backdrop: "4", + }, });