From b64ed411b1b645ae537430ef123be9ffeebc91da Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 8 May 2025 21:33:38 -0400 Subject: [PATCH] artcraft account login/logout (#595) --- .../src/components/signaled/TopBar/TopBar.tsx | 4 + frontend/libs/api/src/index.ts | 1 + .../AccountSettingsPane.tsx | 74 ++++++++--- .../AccountSettings/ArtcraftAccountBlock.tsx | 84 +++++++++++++ .../AccountSettings/SoraAccountBlock.tsx | 119 ++++++++++++++++++ .../AccountSettings}/SoraAccountButton.tsx | 0 .../settings-modal/src/lib/settings-modal.tsx | 43 ++++--- .../src/lib/sora/LogoutSoraSession.ts | 5 +- 8 files changed, 287 insertions(+), 43 deletions(-) rename frontend/libs/components/settings-modal/src/lib/panes/{ => AccountSettings}/AccountSettingsPane.tsx (69%) create mode 100644 frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx create mode 100644 frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountBlock.tsx rename frontend/libs/components/settings-modal/src/lib/{components => panes/AccountSettings}/SoraAccountButton.tsx (100%) diff --git a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx index 835ad885a6..131afd97e4 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx @@ -17,11 +17,14 @@ import { downloadFileFromUrl } from "@storyteller/api"; import { TabSelector, TabItem } from "@storyteller/ui-tab-selector"; import { Signal } from "@preact/signals-react"; import { useSignals } from "@preact/signals-react/runtime"; +import { setLogoutStates } from "~/signals/authentication/utilities"; + function isEditorPath(path: string) { if (path === "/") return true; if (path === "/idealenigma/") return true; return false; } + interface Props { pageName: string; appTabIdSignal: Signal; @@ -118,6 +121,7 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => { setIsSettingsModalOpen(false)} + globalAccountLogoutCallback={() => setLogoutStates()} /> { +interface AccountSettingsPaneProps { + globalAccountLogoutCallback: () => void; +} + +export const AccountSettingsPane = ({ + globalAccountLogoutCallback, +}: AccountSettingsPaneProps) => { const [accountInfo] = useState({ username: "-", subscription: "-", @@ -22,6 +30,7 @@ export const AccountSettingsPane = () => { return ( <>
+ {/*
ArtCraft Account: {accountInfo.username} @@ -44,11 +53,17 @@ export const AccountSettingsPane = () => { third party account.

+ */} -
- OpenAI / Sora Account: - -
+ + + + + + {/* +
+ +

Coming Soon...

Google / Veo Account: @@ -57,20 +72,14 @@ export const AccountSettingsPane = () => {
+
+ Midjourney Account: + +
+
-
- - setPalApiKey((e.target as any).value)} - placeholder="Enter API Key" - disabled - /> -
+
+ + setPalApiKey((e.target as any).value)} + placeholder="Enter API Key" + disabled + /> +
+
+ + setPalApiKey((e.target as any).value)} + placeholder="Enter API Key" + disabled + /> +
+ */}
); diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx new file mode 100644 index 0000000000..8961070381 --- /dev/null +++ b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/ArtcraftAccountBlock.tsx @@ -0,0 +1,84 @@ +import { useState, useEffect } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; +import { Button } from "@storyteller/ui-button"; + +import { UsersApi } from "@storyteller/api"; +import { UserInfo } from "@storyteller/api"; + +const usersApi = new UsersApi(); + +export interface ArtcraftAccountBlockProps { + globalAccountLogoutCallback: () => void, +} + +export const ArtcraftAccountBlock = ({ + globalAccountLogoutCallback, +}: ArtcraftAccountBlockProps) => { + const [artcraftSession, setArtcraftSession] = useState(undefined); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isCheckingArtcraftSession, setIsCheckingArtcraftSession] = useState(false); + + useEffect(() => { + const fetchSession = async () => { + setIsCheckingArtcraftSession(true); + try { + const result = await usersApi.GetSession(); + console.log(">>> result", result); + setArtcraftSession(result?.data?.user); + setIsLoggedIn(result?.data?.loggedIn || false); + } catch (e) { + console.error("Error fetching Artcraft session", e); + setArtcraftSession(undefined); + setIsLoggedIn(false); + } finally { + setIsCheckingArtcraftSession(false); + } + }; + fetchSession(); + }, []); + + const handleArtcraftButton = async () => { + if (isCheckingArtcraftSession) return; + if (isLoggedIn) { + setIsCheckingArtcraftSession(true); + await usersApi.Logout(); + setArtcraftSession(undefined); + setIsLoggedIn(false); + setIsCheckingArtcraftSession(false); + globalAccountLogoutCallback(); // TODO: This resets the old global application state + } else { + window.location.href = "/login"; // TODO(bt,2025-05-08): Once we have in-page routing, get rid of this. + } + }; + + return ( +
+ ArtCraft Account: +
{artcraftSession?.display_name}
+ +
+ ) +} \ No newline at end of file diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountBlock.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountBlock.tsx new file mode 100644 index 0000000000..b88f9c69a1 --- /dev/null +++ b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountBlock.tsx @@ -0,0 +1,119 @@ +import { useState, useEffect } from "react"; +import { Button } from "@storyteller/ui-button"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; +import { + CheckSoraSession, + SoraSessionState, + LogoutSoraSession, + useSoraLoginListener, + CheckSoraSessionResult, +} from "@storyteller/tauri-api"; +import { invoke } from "@tauri-apps/api/core"; + +const DEFAULT_CACHE_TTL_MS = 1 * 60 * 1000; // 1 minute + +interface SoraAccountBlockProps { + cacheKey?: string; + cacheTtlMs?: number; +} + +export const SoraAccountBlock = ({ + cacheKey = "soraSettingsSessionCache", + cacheTtlMs = DEFAULT_CACHE_TTL_MS, +}: SoraAccountBlockProps) => { + const [soraSession, setSoraSession] = useState(undefined); + const [isCheckingSoraSession, setIsCheckingSoraSession] = useState(false); + + useSoraLoginListener(() => { + setIsCheckingSoraSession(true); + CheckSoraSession() + .then((result) => { + setSoraSession(result); + localStorage.setItem( + cacheKey, + JSON.stringify({ session: result, timestamp: Date.now() }) + ); + }) + .finally(() => setIsCheckingSoraSession(false)); + }); + + useEffect(() => { + const fetchSession = async () => { + setIsCheckingSoraSession(true); + try { + const cached = localStorage.getItem(cacheKey); + if (cached) { + const { session, timestamp } = JSON.parse(cached); + if (Date.now() - timestamp < cacheTtlMs) { + setSoraSession(session); + setIsCheckingSoraSession(false); + return; + } + } + const result = await CheckSoraSession(); + setSoraSession(result); + localStorage.setItem( + cacheKey, + JSON.stringify({ session: result, timestamp: Date.now() }) + ); + } catch (e) { + console.error("Error fetching Sora session", e); + setSoraSession(undefined); + } finally { + setIsCheckingSoraSession(false); + } + }; + fetchSession(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [cacheKey, cacheTtlMs]); + + const handleSoraButton = async () => { + if (isCheckingSoraSession) return; + if (soraSession?.state === SoraSessionState.Valid) { + setIsCheckingSoraSession(true); + await LogoutSoraSession(); + setSoraSession({state: SoraSessionState.NotSetUp}); + localStorage.removeItem(cacheKey); + setIsCheckingSoraSession(false); + } else { + setIsCheckingSoraSession(true); + try { + await invoke("open_sora_login_command"); + } catch (e) { + } finally { + setIsCheckingSoraSession(false); + } + } + }; + + return( +
+ OpenAI / Sora Account: +
{soraSession?.maybe_account_email || ""}
+ +
+ ) +} \ No newline at end of file diff --git a/frontend/libs/components/settings-modal/src/lib/components/SoraAccountButton.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountButton.tsx similarity index 100% rename from frontend/libs/components/settings-modal/src/lib/components/SoraAccountButton.tsx rename to frontend/libs/components/settings-modal/src/lib/panes/AccountSettings/SoraAccountButton.tsx diff --git a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx index 39d5e764c7..aaf15eb515 100644 --- a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx +++ b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx @@ -3,52 +3,51 @@ import { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUser, - faVideo, - faImage, faCog, faVolumeHigh, } from "@fortawesome/pro-solid-svg-icons"; import { twMerge } from "tailwind-merge"; import { MiscSettingsPane } from "./panes/MiscSettingsPane"; import { AudioSettingsPane } from "./panes/AudioSettingsPane"; -import { AccountSettingsPane } from "./panes/AccountSettingsPane"; -import { VideoSettingsPane } from "./panes/VideoSettingsPane"; +import { AccountSettingsPane } from "./panes/AccountSettings/AccountSettingsPane"; interface SettingsModalProps { isOpen: boolean; onClose: () => void; + globalAccountLogoutCallback: () => void; } -type SettingsSection = "misc" | "audio" | "accounts" | "video" | "image"; +//type SettingsSection = "misc" | "audio" | "accounts" | "video" | "image"; +type SettingsSection = "general" | "accounts" | "alerts"; -export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => { +export const SettingsModal = ({ isOpen, onClose, globalAccountLogoutCallback }: SettingsModalProps) => { const [selectedSection, setSelectedSection] = - useState("misc"); + useState("general"); const sections = [ - { id: "audio" as const, label: "Audio", icon: faVolumeHigh }, - { id: "misc" as const, label: "Misc", icon: faCog }, + { id: "general" as const, label: "General", icon: faCog }, { id: "accounts" as const, label: "Accounts", icon: faUser }, - { id: "video" as const, label: "Video", icon: faVideo }, - { id: "image" as const, label: "Image", icon: faImage }, + { id: "alerts" as const, label: "Alerts", icon: faVolumeHigh }, + //{ id: "video" as const, label: "Video", icon: faVideo }, + //{ id: "image" as const, label: "Image", icon: faImage }, ]; const renderContent = () => { switch (selectedSection) { - case "audio": + case "alerts": return ; - case "misc": + case "general": return ; case "accounts": - return ; - case "video": - return ; - case "image": - return ( -
- -
- ); + return ; + //case "video": + // return ; + //case "image": + // return ( + //
+ // + //
+ // ); } }; diff --git a/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts b/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts index c52f9ae5d0..eb2be0d3c2 100644 --- a/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts +++ b/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts @@ -1,4 +1,5 @@ +import { invoke } from "@tauri-apps/api/core"; + export const LogoutSoraSession = async (): Promise => { - // Replace with real Tauri command to logout Sora session - BFlat - return new Promise((resolve) => setTimeout(resolve, 500)); + return await invoke("sora_logout_command"); };