mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 08:57:54 +00:00
artcraft account login/logout (#595)
This commit is contained in:
1 parent
a155b419e1
commit
b64ed411b1
8 files changed
+287
-43
No files matched your search
@@ -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<string>;
|
||||
@@ -118,6 +121,7 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => {
|
||||
<SettingsModal
|
||||
isOpen={isSettingsModalOpen}
|
||||
onClose={() => setIsSettingsModalOpen(false)}
|
||||
globalAccountLogoutCallback={() => setLogoutStates()}
|
||||
/>
|
||||
|
||||
<GalleryModal
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./lib/enums/index.js";
|
||||
export * from "./lib/models/Users.js";
|
||||
export * from "./lib/AnalyticsApi.js";
|
||||
export * from "./lib/BillingApi.js";
|
||||
export * from "./lib/EngineApi.js";
|
||||
|
||||
+55
-19
@@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { SoraAccountButton } from "../components/SoraAccountButton";
|
||||
import { SoraAccountButton } from "./SoraAccountButton";
|
||||
import { SoraAccountBlock } from "./SoraAccountBlock";
|
||||
import { ArtcraftAccountBlock } from "./ArtcraftAccountBlock";
|
||||
|
||||
interface AccountInfo {
|
||||
username: string;
|
||||
@@ -9,7 +11,13 @@ interface AccountInfo {
|
||||
credits: number;
|
||||
}
|
||||
|
||||
export const AccountSettingsPane = () => {
|
||||
interface AccountSettingsPaneProps {
|
||||
globalAccountLogoutCallback: () => void;
|
||||
}
|
||||
|
||||
export const AccountSettingsPane = ({
|
||||
globalAccountLogoutCallback,
|
||||
}: AccountSettingsPaneProps) => {
|
||||
const [accountInfo] = useState<AccountInfo>({
|
||||
username: "-",
|
||||
subscription: "-",
|
||||
@@ -22,6 +30,7 @@ export const AccountSettingsPane = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
{/*
|
||||
<div className="flex justify-between">
|
||||
<span>ArtCraft Account:</span>
|
||||
<span className="text-white/80">{accountInfo.username}</span>
|
||||
@@ -44,11 +53,17 @@ export const AccountSettingsPane = () => {
|
||||
third party account.
|
||||
</p>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>OpenAI / Sora Account:</span>
|
||||
<SoraAccountButton />
|
||||
</div>
|
||||
<ArtcraftAccountBlock globalAccountLogoutCallback={globalAccountLogoutCallback}/>
|
||||
|
||||
<SoraAccountBlock />
|
||||
|
||||
|
||||
{/*
|
||||
<hr />
|
||||
|
||||
<h2>Coming Soon...</h2>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>Google / Veo Account:</span>
|
||||
@@ -57,20 +72,14 @@ export const AccountSettingsPane = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>Midjourney Account:</span>
|
||||
<Button variant="secondary" disabled className="h-[30px]">
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="pal-api-key" className="mb-2 block">
|
||||
Pal API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="pal-api-key"
|
||||
type="password"
|
||||
value={palApiKey}
|
||||
onChange={(e) => setPalApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="kling-api-key" className="mb-2 block">
|
||||
Kling API Key (optional)
|
||||
@@ -84,7 +93,34 @@ export const AccountSettingsPane = () => {
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="pal-api-key" className="mb-2 block">
|
||||
Fal API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="pal-api-key"
|
||||
type="password"
|
||||
value={palApiKey}
|
||||
onChange={(e) => setPalApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="pal-api-key" className="mb-2 block">
|
||||
Replicate API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="pal-api-key"
|
||||
type="password"
|
||||
value={palApiKey}
|
||||
onChange={(e) => setPalApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
+84
@@ -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<UserInfo| undefined>(undefined);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(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 (
|
||||
<div className="flex justify-between items-center">
|
||||
<span>ArtCraft Account:</span>
|
||||
<pre>{artcraftSession?.display_name}</pre>
|
||||
<Button
|
||||
variant={
|
||||
isLoggedIn && !isCheckingArtcraftSession
|
||||
? "destructive"
|
||||
: !isLoggedIn
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
className="h-[30px]"
|
||||
onClick={handleArtcraftButton}
|
||||
disabled={isCheckingArtcraftSession}
|
||||
>
|
||||
{isCheckingArtcraftSession ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-sm"
|
||||
/>
|
||||
) : isLoggedIn ? (
|
||||
"Log Out"
|
||||
) : (
|
||||
"Log In"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+119
@@ -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<CheckSoraSessionResult| undefined>(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(
|
||||
<div className="flex justify-between items-center">
|
||||
<span>OpenAI / Sora Account:</span>
|
||||
<pre>{soraSession?.maybe_account_email || ""}</pre>
|
||||
<Button
|
||||
variant={
|
||||
soraSession?.state === SoraSessionState.Valid && !isCheckingSoraSession
|
||||
? "destructive"
|
||||
: soraSession?.state === SoraSessionState.NotSetUp
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
className="h-[30px]"
|
||||
onClick={handleSoraButton}
|
||||
disabled={isCheckingSoraSession}
|
||||
>
|
||||
{isCheckingSoraSession ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-sm"
|
||||
/>
|
||||
) : soraSession?.state === SoraSessionState.Valid ? (
|
||||
"Disconnect"
|
||||
) : (
|
||||
"Connect"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File renamed without changes.
@@ -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<SettingsSection>("misc");
|
||||
useState<SettingsSection>("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 <AudioSettingsPane />;
|
||||
case "misc":
|
||||
case "general":
|
||||
return <MiscSettingsPane />;
|
||||
case "accounts":
|
||||
return <AccountSettingsPane />;
|
||||
case "video":
|
||||
return <VideoSettingsPane />;
|
||||
case "image":
|
||||
return (
|
||||
<div>
|
||||
<button className="text-blue-600">Various Image Settings...</button>
|
||||
</div>
|
||||
);
|
||||
return <AccountSettingsPane globalAccountLogoutCallback={globalAccountLogoutCallback} />;
|
||||
//case "video":
|
||||
// return <VideoSettingsPane />;
|
||||
//case "image":
|
||||
// return (
|
||||
// <div>
|
||||
// <button className="text-blue-600">Various Image Settings...</button>
|
||||
// </div>
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export const LogoutSoraSession = async (): Promise<void> => {
|
||||
// Replace with real Tauri command to logout Sora session - BFlat
|
||||
return new Promise((resolve) => setTimeout(resolve, 500));
|
||||
return await invoke("sora_logout_command");
|
||||
};
|
||||
Reference in new issue
Block a user