mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 03:21:23 +00:00
reverted main but removed zustand state
This commit is contained in:
1 parent
eb2b6bb870
commit
f2d9d5b59f
5 files changed
+218
-88
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
faGear,
|
||||
faImages,
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { AuthButtons } from "./AuthButtons";
|
||||
import { SceneTitleInput } from "./SceneTitleInput";
|
||||
import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity";
|
||||
import {
|
||||
@@ -58,6 +57,10 @@ import {
|
||||
usePricingModalStore,
|
||||
useCreditsModalStore,
|
||||
} from "@storyteller/ui-pricing-modal";
|
||||
import { useCreditsBalanceChangedEvent } from "@storyteller/tauri-events";
|
||||
import { useSubscriptionPlanChangedEvent } from "@storyteller/tauri-events";
|
||||
import { useCreditsState } from "@storyteller/credits";
|
||||
import { useSubscriptionState } from "@storyteller/subscription";
|
||||
|
||||
interface Props {
|
||||
pageName: string;
|
||||
@@ -76,7 +79,7 @@ type SettingsSection =
|
||||
const SWITCHER_THROTTLE_TIME = 500; // milliseconds
|
||||
|
||||
// NB: See `TabState` for the default tab.
|
||||
const appMenuTabs: MenuIconItem<TabId>[] = [
|
||||
const appMenuTabs: MenuIconItem[] = [
|
||||
{
|
||||
id: "IMAGE",
|
||||
label: "Text to Image",
|
||||
@@ -150,6 +153,26 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
const [disableSwitcher, setDisableSwitcher] = useState(false);
|
||||
const switcherThrottle = useRef(false);
|
||||
|
||||
const creditsStore = useCreditsState();
|
||||
const sumTotalCredits = creditsStore.totalCredits;
|
||||
|
||||
//const subscriptionStore = useSubscriptionState();
|
||||
//const hasPaidPlan = subscriptionStore.hasPaidPlan();
|
||||
const hasPaidPlan = false;
|
||||
|
||||
useEffect(() => {
|
||||
creditsStore.fetchFromServer();
|
||||
//subscriptionStore.fetchFromServer();
|
||||
}, []);
|
||||
|
||||
useCreditsBalanceChangedEvent(async () => {
|
||||
creditsStore.fetchFromServer();
|
||||
});
|
||||
|
||||
useSubscriptionPlanChangedEvent(async () => {
|
||||
//subscriptionStore.fetchFromServer();
|
||||
});
|
||||
|
||||
const disableTabSwitcher = () => {
|
||||
return (
|
||||
disableSwitcher ||
|
||||
@@ -218,13 +241,43 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
|
||||
const pageTitle = getPageTitle();
|
||||
|
||||
const { toggleModal } = usePricingModalStore();
|
||||
const { toggleModal: toggleSubscriptionModal } = usePricingModalStore();
|
||||
const { toggleModal: toggleCreditsModal } = useCreditsModalStore();
|
||||
|
||||
// Pick logo based on current theme (light uses black logo; others use white)
|
||||
const [logoSrc, setLogoSrc] = useState<string>(
|
||||
"/resources/logo/artcraft-logo-color-white.svg",
|
||||
);
|
||||
useEffect(() => {
|
||||
const computeLogo = () => {
|
||||
const root = document.documentElement;
|
||||
const isLight = root.classList.contains("theme-light");
|
||||
setLogoSrc(
|
||||
isLight
|
||||
? "/resources/logo/artcraft-logo-color-black.svg"
|
||||
: "/resources/logo/artcraft-logo-color-white.svg",
|
||||
);
|
||||
};
|
||||
computeLogo();
|
||||
const mo = new MutationObserver((muts) => {
|
||||
for (const m of muts) {
|
||||
if (m.type === "attributes" && m.attributeName === "class") {
|
||||
computeLogo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
mo.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
return () => mo.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className="fixed left-0 top-0 z-[60] w-full border-b border-white/5 bg-ui-background"
|
||||
className="fixed left-0 top-0 z-[60] w-full border-b border-ui-panel-border bg-ui-background"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<nav
|
||||
@@ -239,8 +292,8 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
</span>
|
||||
<img
|
||||
className="h-[24px] w-auto"
|
||||
src="/resources/images/artcraft-logo-3.png"
|
||||
alt="Logo ArtCraft"
|
||||
src={logoSrc}
|
||||
alt="ArtCraft Logo"
|
||||
data-tauri-drag-region
|
||||
/>
|
||||
</div>
|
||||
@@ -264,7 +317,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
} else {
|
||||
set3DPageMounted(false);
|
||||
}
|
||||
useTabStore.getState().setActiveTab(tabId);
|
||||
useTabStore.getState().setActiveTab(tabId as TabId);
|
||||
setTimeout(() => {
|
||||
// Clear the throttle
|
||||
switcherThrottle.current = false;
|
||||
@@ -283,7 +336,9 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
{tabStore.activeTabId === "3D" ? (
|
||||
<SceneTitleInput pageName={pageName} />
|
||||
) : (
|
||||
<h1 data-tauri-drag-region>{pageTitle}</h1>
|
||||
<h1 className="text-base-fg" data-tauri-drag-region>
|
||||
{pageTitle}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -300,20 +355,20 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
}
|
||||
triggerLabel={
|
||||
<span className="whitespace-nowrap text-sm font-medium">
|
||||
180 Credits
|
||||
{sumTotalCredits} Credits
|
||||
</span>
|
||||
}
|
||||
buttonClassName="h-[30px] px-2 ps-1.5 bg-transparent hover:bg-white/10"
|
||||
panelClassName="mt-3"
|
||||
buttonClassName="h-[30px] px-2 ps-1.5 bg-transparent hover:bg-ui-controls/30 border-0 shadow-none"
|
||||
panelClassName="mt-3 bg-ui-panel border border-ui-panel-border text-base-fg"
|
||||
>
|
||||
{(close) => (
|
||||
<div className="w-72 p-2.5 text-white">
|
||||
<div className="text-base-fg w-72 p-2.5">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium text-white/80">
|
||||
<span className="text-base-fg/80 flex items-center gap-1.5 text-sm font-medium">
|
||||
Your credit balance
|
||||
</span>
|
||||
<button
|
||||
className="text-sm font-medium text-primary-300 transition-all hover:text-primary-200"
|
||||
className="text-sm font-medium text-primary-400 transition-all hover:text-primary-300"
|
||||
onClick={() => {
|
||||
close();
|
||||
toggleCreditsModal();
|
||||
@@ -322,17 +377,17 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
Buy credits
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-4xl font-bold">
|
||||
<div className="text-base-fg flex items-center gap-2 text-4xl font-bold">
|
||||
<FontAwesomeIcon
|
||||
icon={faCoinFront}
|
||||
className="text-2xl text-primary"
|
||||
/>
|
||||
180
|
||||
{sumTotalCredits}
|
||||
</div>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<Button
|
||||
variant="action"
|
||||
className="h-9 grow bg-white/15 hover:bg-white/20"
|
||||
className="h-9 grow"
|
||||
onClick={() => {
|
||||
close();
|
||||
handleOpenBillingSettings();
|
||||
@@ -345,7 +400,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
className="h-9 grow"
|
||||
onClick={() => {
|
||||
close();
|
||||
toggleModal();
|
||||
toggleSubscriptionModal();
|
||||
}}
|
||||
icon={faGem}
|
||||
>
|
||||
@@ -356,14 +411,16 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
)}
|
||||
</PopoverMenu>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faGem}
|
||||
onClick={toggleModal}
|
||||
className="h-[38px] shadow-md shadow-primary-500/50 transition-all duration-300 hover:shadow-md hover:shadow-primary-500/75"
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
{!hasPaidPlan && (
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faGem}
|
||||
onClick={toggleSubscriptionModal}
|
||||
className="h-[38px] shadow-md shadow-primary-500/50 transition-all duration-300 hover:shadow-md hover:shadow-primary-500/75"
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Tooltip content="Settings" position="bottom" delay={300}>
|
||||
<Button
|
||||
@@ -384,28 +441,30 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
icon={faImages}
|
||||
onClick={handleOpenGalleryModal}
|
||||
>
|
||||
<span className="hidden whitespace-nowrap xl:block">
|
||||
<span className="text-base-fg hidden whitespace-nowrap xl:block">
|
||||
My Library
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<Activity />
|
||||
</div>
|
||||
|
||||
<div className="no-drag">
|
||||
<AuthButtons loginSignUpPressed={loginSignUpPressed} />
|
||||
{/* TODO(bt,2025-09-12): This was the old auth buttons that didn't work. We need to remove this and clean up the DOM. */}
|
||||
</div>
|
||||
|
||||
{isDesktop && platform !== "macos" && (
|
||||
<div className="no-drag flex items-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-white/10 hover:text-white"
|
||||
className="text-base-fg h-[32px] w-[44px] rounded-none border-0 bg-transparent opacity-70 shadow-none hover:bg-ui-controls/20 hover:opacity-100"
|
||||
onClick={minimize}
|
||||
>
|
||||
<FontAwesomeIcon icon={faDash} className="text-xs" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-white/10 hover:text-white"
|
||||
className="text-base-fg h-[32px] w-[44px] rounded-none border-0 bg-transparent opacity-70 shadow-none hover:bg-ui-controls/20 hover:opacity-100"
|
||||
onClick={toggleMaximize}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
@@ -415,7 +474,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-red/40 hover:text-white"
|
||||
className="text-base-fg h-[32px] w-[44px] rounded-none border-0 bg-transparent opacity-70 shadow-none hover:bg-red/10 hover:text-red"
|
||||
onClick={close}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-lg" />
|
||||
|
||||
@@ -61,9 +61,6 @@
|
||||
{
|
||||
"path": "../../libs/soundboard"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-events"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/slider-v2"
|
||||
},
|
||||
@@ -88,6 +85,15 @@
|
||||
{
|
||||
"path": "../../libs/common"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/state/subscription"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/state/credits"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-events"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/pricing-modal"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import {
|
||||
@@ -9,49 +9,55 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { usePricingModalStore } from "@storyteller/ui-pricing-modal";
|
||||
import { useCreditsModalStore } from "@storyteller/ui-pricing-modal";
|
||||
import { useCreditsState, CreditsState } from "@storyteller/credits";
|
||||
import {
|
||||
FREE_PLAN,
|
||||
SubscriptionPlanDetails,
|
||||
useSubscriptionState,
|
||||
} from "@storyteller/subscription";
|
||||
import { SUBSCRIPTION_PLANS_BY_SLUG } from "@storyteller/subscription";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface BillingSettingsPaneProps {}
|
||||
|
||||
interface BillingInfo {
|
||||
credits: {
|
||||
remaining: number;
|
||||
total: number;
|
||||
};
|
||||
plan: string;
|
||||
nextPayment: {
|
||||
amount: string;
|
||||
date: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
|
||||
const [billingInfo] = useState<BillingInfo>({
|
||||
credits: {
|
||||
remaining: 180,
|
||||
total: 1000,
|
||||
},
|
||||
plan: "Pro Plan",
|
||||
nextPayment: {
|
||||
amount: "$99",
|
||||
date: "Oct 18",
|
||||
},
|
||||
});
|
||||
const { toggleModal: toggleSubscriptionModal } = usePricingModalStore();
|
||||
|
||||
//const creditsStore = useCreditsState();
|
||||
//const sumTotalCredits = creditsStore.totalCredits;
|
||||
const sumTotalCredits = 100;
|
||||
|
||||
//const subscriptionStore = useSubscriptionState();
|
||||
|
||||
//const maybePlanSlug = subscriptionStore.subscriptionInfo?.productSlug;
|
||||
const maybePlanSlug = "free";
|
||||
|
||||
const currentPlanDetails: SubscriptionPlanDetails = maybePlanSlug
|
||||
? SUBSCRIPTION_PLANS_BY_SLUG.get(maybePlanSlug) || FREE_PLAN
|
||||
: FREE_PLAN;
|
||||
|
||||
//const canCancelPlan = subscriptionStore.canCancelPlan();
|
||||
const canCancelPlan = false;
|
||||
|
||||
//const nextBillAt =
|
||||
// subscriptionStore.subscriptionInfo?.nextBillAt?.toLocaleDateString();
|
||||
const nextBillAt = "";
|
||||
//const subscriptionEndAt =
|
||||
// subscriptionStore.subscriptionInfo?.subscriptionEndAt?.toLocaleDateString();
|
||||
const subscriptionEndAt = "";
|
||||
|
||||
const changeOrUpgradePlanButtonLabel = canCancelPlan
|
||||
? "Change plan"
|
||||
: "Upgrade plan";
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBillingData = async () => {
|
||||
// TODO: Replace with actual API call - BFlat
|
||||
// const data = await GetBillingInfo();
|
||||
// setBillingInfo(data.payload);
|
||||
};
|
||||
fetchBillingData();
|
||||
//creditsStore.fetchFromServer();
|
||||
//subscriptionStore.fetchFromServer();
|
||||
}, []);
|
||||
|
||||
const { toggleModal } = usePricingModalStore();
|
||||
const { toggleModal: toggleCreditsModal } = useCreditsModalStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 text-base-fg">
|
||||
<div className="space-y-2">
|
||||
<Label>Current Plan</Label>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -60,32 +66,45 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
|
||||
icon={faStar}
|
||||
className="text-[#C03FFF] text-lg"
|
||||
/>
|
||||
{billingInfo.plan}
|
||||
{currentPlanDetails.name}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" className="h-[30px]">
|
||||
Cancel plan
|
||||
</Button>
|
||||
{canCancelPlan && <CancelPlanButton />}
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
className="h-[30px]"
|
||||
onClick={() => {
|
||||
toggleModal();
|
||||
}}
|
||||
onClick={() => toggleSubscriptionModal()}
|
||||
>
|
||||
Upgrade plan
|
||||
{changeOrUpgradePlanButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TODO(bt): expose this information via API
|
||||
<div className="flex items-center gap-2 text-white/50">
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
Next {billingInfo.nextPayment.amount} payment due{" "}
|
||||
{billingInfo.nextPayment.date}
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<hr className="border-white/10" />
|
||||
{subscriptionEndAt && (
|
||||
<div className="flex items-center gap-2 text-white/50">
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
Subscription ends on {subscriptionEndAt}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nextBillAt && (
|
||||
<div className="flex items-center gap-2 text-white/50">
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
Next payment on {nextBillAt}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr className="border-ui-panel-border" />
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Label htmlFor="credits" className="flex items-center gap-2">
|
||||
@@ -97,22 +116,56 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
|
||||
icon={faCoinFront}
|
||||
className="text-primary text-lg"
|
||||
/>
|
||||
<span className="text-2xl font-bold">
|
||||
{billingInfo.credits.remaining}
|
||||
</span>
|
||||
<span className="text-2xl font-bold">{sumTotalCredits}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
className="h-[30px]"
|
||||
onClick={() => toggleCreditsModal()}
|
||||
>
|
||||
Buy credits
|
||||
</Button>
|
||||
<BuyCreditsButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*<CreditsTally creditsStore={creditsStore} />*/}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CancelPlanButton = () => {
|
||||
const handleClick = async () => {
|
||||
await invoke("storyteller_open_customer_portal_cancel_plan_command");
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant="secondary" className="h-[30px]" onClick={handleClick}>
|
||||
Cancel plan
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const BuyCreditsButton = () => {
|
||||
const { toggleModal: toggleCreditsModal } = useCreditsModalStore();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="h-[30px]"
|
||||
onClick={() => toggleCreditsModal()}
|
||||
>
|
||||
Buy credits
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const CreditsTally = ({ creditsStore }: { creditsStore: CreditsState }) => {
|
||||
return (
|
||||
<div className="flex pl-5 pt-3">
|
||||
<ul className="list-disc">
|
||||
<li>
|
||||
{" "}
|
||||
{creditsStore.monthlyCredits} monthly credits (refilled monthly){" "}
|
||||
</li>
|
||||
<li> {creditsStore.bankedCredits} purchased credits </li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,12 @@
|
||||
{
|
||||
"path": "../../model-list"
|
||||
},
|
||||
{
|
||||
"path": "../../state/subscription"
|
||||
},
|
||||
{
|
||||
"path": "../../state/credits"
|
||||
},
|
||||
{
|
||||
"path": "../pricing-modal"
|
||||
},
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
{
|
||||
"path": "../../model-list/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../state/subscription/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../state/credits/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../pricing-modal/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user