add frontend billing package, Tauri "get credits command"

This commit is contained in:
Brandon Thomas committed 2025-09-12 17:17:43 -04:00
1 parent 5360dd0f58
commit 64b91cd6bc
20 files changed
+575 -65

No files matched your search

@@ -40,3 +40,14 @@ impl From<&str> for CommandErrorResponseWrapper<(), ()> {
}
}
}
impl From<String> for CommandErrorResponseWrapper<(), ()> {
fn from(value: String) -> Self {
CommandErrorResponseWrapper {
status: CommandErrorStatus::BadRequest, // NB: Default to 500 error.
error_message: Some(value),
error_type: None,
error_details: None,
}
}
}
@@ -1,2 +1,3 @@
pub mod storyteller_open_subscription_purchase_command;
pub mod storyteller_open_credits_purchase_command;
pub mod storyteller_get_credits_command;
pub mod storyteller_open_credits_purchase_command;
pub mod storyteller_open_subscription_purchase_command;
@@ -0,0 +1,74 @@
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{ContextualImageEditModel, EditImageQuality, EditImageSize};
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::state::provider_priority::Provider;
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
use crate::services::midjourney::windows::open_midjourney_login_window::open_midjourney_login_window;
use crate::services::sora::windows::sora_login_window::open_sora_login_window::open_sora_login_window;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use crate::services::storyteller::windows::open_storyteller_billing_window::{open_storyteller_billing_window, OpenStorytellerBillingWindowArgs, ProductInfo};
use artcraft_api_defs::stripe_artcraft::create_subscription_checkout::PlanBillingCadence;
use enums::common::artcraft_credits_pack_slug::ArtcraftCreditsPackSlug;
use enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug;
use enums::common::payments_namespace::PaymentsNamespace;
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
use errors::AnyhowResult;
use log::{error, info};
use serde_derive::{Deserialize, Serialize};
use storyteller_client::credits::get_session_credits::get_session_credits;
use tauri::{AppHandle, State};
use crate::core::commands::providers::get_provider_order_command::GetProviderOrderResponse;
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
// TODO(bt,2025-09-12): Cache credits and clear cache on generate calls.
#[derive(Serialize)]
pub struct GetCreditsResponse {
pub free_credits: u64,
pub monthly_credits: u64,
pub banked_credits: u64,
pub sum_total_credits: u64,
}
impl SerializeMarker for GetCreditsResponse {}
#[tauri::command]
pub async fn storyteller_get_credits_command(
app_env_configs: State<'_, AppEnvConfigs>,
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
) -> ResponseOrErrorMessage<GetCreditsResponse> {
info!("storyteller_get_credits_command called");
let credits = get_credits(
&app_env_configs,
&storyteller_creds_manager)
.await
.map_err(|err| {
error!("Error getting credits: {:?}", err);
format!("Error getting credits: {:?}", err)
})?;
Ok(credits.into())
}
async fn get_credits(
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> AnyhowResult<GetCreditsResponse> {
let maybe_creds = storyteller_creds_manager.get_credentials()?;
let response = get_session_credits(
&app_env_configs.storyteller_host,
maybe_creds.as_ref(),
PaymentsNamespace::Artcraft,
).await?;
Ok(GetCreditsResponse {
free_credits: response.free_credits,
monthly_credits: response.monthly_credits,
banked_credits: response.banked_credits,
sum_total_credits: response.sum_total_credits,
})
}
@@ -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 {
@@ -393,6 +392,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
</div>
<div className="no-drag">
{/* 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" && (
+3
View File
@@ -91,6 +91,9 @@
{
"path": "../../libs/common"
},
{
"path": "../../libs/billing"
},
{
"path": "../../libs/analytics/google-analytics"
},
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# @frontend/billing
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @frontend/billing` to execute the unit tests via [Vitest](https://vitest.dev/).
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@storyteller/billing",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './lib/billing';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import FrontendBilling from './billing';
describe('FrontendBilling', () => {
it('should render successfully', () => {
const { baseElement } = render(<FrontendBilling />);
expect(baseElement).toBeTruthy();
});
});
+15
View File
@@ -0,0 +1,15 @@
import styles from './billing.module.css';
export function FrontendBilling() {
return (
<div className={styles['container']}>
<h1>Welcome to FrontendBilling!</h1>
</div>
);
}
export default FrontendBilling;
+22
View File
@@ -0,0 +1,22 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../tauri-api"
},
{
"path": "../tauri-utils"
},
{
"path": "../tauri-events"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}
+56
View File
@@ -0,0 +1,56 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"references": [
{
"path": "../tauri-api/tsconfig.lib.json"
},
{
"path": "../tauri-utils/tsconfig.lib.json"
},
{
"path": "../tauri-events/tsconfig.lib.json"
}
]
}
+34
View File
@@ -0,0 +1,34 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
+49
View File
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/billing',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@frontend/billing',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+1
View File
@@ -1,3 +1,4 @@
export * from "./lib/artcraft/ArtcraftGetCredits.js";
export * from "./lib/common/CommandStatus.js";
export * from "./lib/enqueue/EnqueueContextualEditImage.js";
export * from "./lib/enqueue/EnqueueImageBgRemovalCommand.js";
@@ -0,0 +1,24 @@
import { invoke } from "@tauri-apps/api/core";
import { CommandResult } from "../common/CommandStatus";
export interface ArtcraftGetCreditsSuccess extends CommandResult {
payload: ArtcraftGetCreditsPayload;
}
export interface ArtcraftGetCreditsPayload {
free_credits: number,
monthly_credits: number,
banked_credits: number,
sum_total_credits: number,
}
// Returns the Success and Error variants directly.
// Throws on Network/Tauri errors.
export const ArtcraftGetCredits = async () : Promise<ArtcraftGetCreditsSuccess> => {
try {
return await invoke("storyteller_get_credits_command") as ArtcraftGetCreditsSuccess;
} catch (error) {
// NB: Endpoint should be infalliable
throw error;
}
}
+222 -62
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -161,6 +161,9 @@
},
{
"path": "./libs/components/provider-billing-modal"
},
{
"path": "./libs/billing"
}
]
}