From f365170dedb90e8520c7713ec997118783c2e3f1 Mon Sep 17 00:00:00 2001 From: Andre <200930841+Andre-Bzk@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:14:59 +0200 Subject: [PATCH] feat: Account-Screen mit Nutzerprofil, App-Version aus package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AccountScreen zeigt jetzt Profilbild, Anzeigename und @Login des eingeloggten Twitch-Nutzers (via neuem IPC-Kanal twitch:get-own-user) - CSS: account__avatar zeigt Profilbild, neue Klassen für identity, name, login und error --- electron.vite.config.ts | 27 +++++++++++ package.json | 2 +- src/main/ipc/handlers.ts | 2 + src/main/twitch/helixClient.ts | 11 +++++ src/main/twitch/types.ts | 7 +++ src/preload/index.ts | 12 ++++- src/renderer/src/screens/AccountScreen.tsx | 54 ++++++++++++++++++++-- src/renderer/src/styles/global.css | 36 +++++++++++++++ src/renderer/src/types/t4sd.d.ts | 8 ++++ 9 files changed, 154 insertions(+), 5 deletions(-) diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 81a38da..aa6438d 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -1,9 +1,30 @@ +import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { defineConfig, externalizeDepsPlugin } from 'electron-vite' import react from '@vitejs/plugin-react' +interface PackageJson { + version?: string +} + +function getAppVersion(): string { + try { + const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf8')) as PackageJson + if (pkg.version) return pkg.version + } catch { + // ignore malformed package.json and use final fallback below + } + + return '0.0.0-dev' +} + +const appVersion = getAppVersion() + export default defineConfig({ main: { + define: { + 'process.env.APP_VERSION': JSON.stringify(appVersion) + }, plugins: [externalizeDepsPlugin()], build: { rollupOptions: { @@ -12,6 +33,9 @@ export default defineConfig({ } }, preload: { + define: { + 'process.env.APP_VERSION': JSON.stringify(appVersion) + }, plugins: [externalizeDepsPlugin()], build: { rollupOptions: { @@ -21,6 +45,9 @@ export default defineConfig({ }, renderer: { root: resolve(__dirname, 'src/renderer'), + define: { + 'process.env.APP_VERSION': JSON.stringify(appVersion) + }, plugins: [react()], resolve: { alias: { diff --git a/package.json b/package.json index f93e896..b2dcd81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twitch4steamdeck", - "version": "0.0.1", + "version": "0.3.2", "description": "Werbefreier Twitch-Client für das Steam Deck (Live & VOD, Big-Screen-UI)", "main": "out/main/index.js", "author": "twitch4steamdeck", diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts index 03aca0c..b58d6a6 100644 --- a/src/main/ipc/handlers.ts +++ b/src/main/ipc/handlers.ts @@ -16,6 +16,7 @@ export const IPC = { authEvent: 'auth:event', twitchGetFollowed: 'twitch:get-followed', + twitchGetOwnUser: 'twitch:get-own-user', twitchGetVideos: 'twitch:get-videos', twitchGetTopGames: 'twitch:get-top-games', twitchGetTopStreams: 'twitch:get-top-streams', @@ -61,6 +62,7 @@ export function registerIpcHandlers( }) ipcMain.handle(IPC.twitchGetFollowed, () => helix.getFollowedWithLiveStatus()) + ipcMain.handle(IPC.twitchGetOwnUser, () => helix.getOwnUserInfo()) ipcMain.handle( IPC.twitchGetTopGames, (_e, cursor?: string) => helix.getTopGames(40, cursor) diff --git a/src/main/twitch/helixClient.ts b/src/main/twitch/helixClient.ts index 68029ef..913fc87 100644 --- a/src/main/twitch/helixClient.ts +++ b/src/main/twitch/helixClient.ts @@ -5,6 +5,7 @@ import type { HelixGame, HelixPaginatedResponse, HelixStream, + OwnUserInfo, TopStreamsResult, HelixUser, HelixVideo, @@ -62,6 +63,16 @@ export class HelixClient { return user } + async getOwnUserInfo(): Promise { + const user = await this.getOwnUser() + return { + id: user.id, + login: user.login, + displayName: user.display_name, + profileImageUrl: user.profile_image_url + } + } + private async getOwnUserId(): Promise { if (this.cachedUserId) return this.cachedUserId const user = await this.getOwnUser() diff --git a/src/main/twitch/types.ts b/src/main/twitch/types.ts index 4fe5a91..03d39ed 100644 --- a/src/main/twitch/types.ts +++ b/src/main/twitch/types.ts @@ -5,6 +5,13 @@ export interface HelixUser { profile_image_url: string } +export interface OwnUserInfo { + id: string + login: string + displayName: string + profileImageUrl: string +} + export interface HelixFollowedChannel { broadcaster_id: string broadcaster_login: string diff --git a/src/preload/index.ts b/src/preload/index.ts index 6154ecf..4484b41 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -10,6 +10,7 @@ const IPC = { authEvent: 'auth:event', twitchGetFollowed: 'twitch:get-followed', + twitchGetOwnUser: 'twitch:get-own-user', twitchGetVideos: 'twitch:get-videos', twitchGetTopGames: 'twitch:get-top-games', twitchGetTopStreams: 'twitch:get-top-streams', @@ -62,6 +63,13 @@ export interface FollowedChannelInfo { language?: string } +export interface OwnUserInfo { + id: string + login: string + displayName: string + profileImageUrl: string +} + export interface PlaybackEvent { kind: 'started' | 'stopped' | 'error' channelLogin?: string @@ -103,7 +111,7 @@ export interface TopStreamsResult { } const api = { - appVersion: process.env.npm_package_version ?? '0.0.0', + appVersion: process.env.APP_VERSION ?? process.env.npm_package_version ?? '0.0.0-dev', app: { quit: (): Promise => ipcRenderer.invoke(IPC.appQuit) }, @@ -122,6 +130,8 @@ const api = { twitch: { getFollowed: (): Promise => ipcRenderer.invoke(IPC.twitchGetFollowed), + getOwnUser: (): Promise => + ipcRenderer.invoke(IPC.twitchGetOwnUser), getVideos: (broadcasterId: string): Promise => ipcRenderer.invoke(IPC.twitchGetVideos, broadcasterId), getTopGames: (cursor?: string): Promise<{ games: GameInfo[]; cursor?: string }> => diff --git a/src/renderer/src/screens/AccountScreen.tsx b/src/renderer/src/screens/AccountScreen.tsx index 8b127d8..55ac86e 100644 --- a/src/renderer/src/screens/AccountScreen.tsx +++ b/src/renderer/src/screens/AccountScreen.tsx @@ -1,5 +1,6 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { UserIcon } from '../components/Icons' +import type { OwnUserInfo } from '../types/t4sd' interface Props { hasFocus: boolean @@ -9,11 +10,40 @@ interface Props { export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }: Props): JSX.Element { const logoutBtnRef = useRef(null) + const [user, setUser] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState('') useEffect(() => { if (hasFocus) logoutBtnRef.current?.focus({ preventScroll: true }) }, [hasFocus]) + useEffect(() => { + let cancelled = false + + setIsLoading(true) + setError('') + + void window.t4sd.twitch + .getOwnUser() + .then((nextUser) => { + if (!cancelled) setUser(nextUser) + }) + .catch((err) => { + if (!cancelled) { + setUser(null) + setError(err instanceof Error ? err.message : 'Accountdaten konnten nicht geladen werden.') + } + }) + .finally(() => { + if (!cancelled) setIsLoading(false) + }) + + return () => { + cancelled = true + } + }, []) + useEffect(() => { if (!hasFocus) return const onKey = (e: KeyboardEvent): void => { @@ -31,6 +61,8 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }: onLogout() } + const showLogin = user && user.login.toLowerCase() !== user.displayName.toLowerCase() + return (
@@ -38,9 +70,25 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }:
- + {user?.profileImageUrl ? ( + + ) : ( + + )} +
+
+

Eingeloggt bei Twitch

+ {isLoading ? ( +

Lade Account…

+ ) : user ? ( + <> +

{user.displayName}

+ {showLogin &&

@{user.login}

} + + ) : ( +

{error || 'Accountdaten konnten nicht geladen werden.'}

+ )}
-

Eingeloggt bei Twitch

App-Version {window.t4sd.appVersion}