mirror repo (WIP)

Need to remove secrets before entire repo can be made public.
This commit is contained in:
Brandon Thomas
2025-09-18 22:55:03 -04:00
commit 0daf83dba9
8735 changed files with 2963896 additions and 0 deletions
+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/).
+2
View File
@@ -0,0 +1,2 @@
export * from './lib/state/credits-state';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAC"}
+63
View File
@@ -0,0 +1,63 @@
import d from "react";
const u = (t) => {
let e;
const r = /* @__PURE__ */ new Set(), o = (a, c) => {
const s = typeof a == "function" ? a(e) : a;
if (!Object.is(s, e)) {
const D = e;
e = c ?? (typeof s != "object" || s === null) ? s : Object.assign({}, e, s), r.forEach((S) => S(e, D));
}
}, n = () => e, i = { setState: o, getState: n, getInitialState: () => E, subscribe: (a) => (r.add(a), () => r.delete(a)) }, E = e = t(o, n, i);
return i;
}, W = (t) => t ? u(t) : u, y = (t) => t;
function O(t, e = y) {
const r = d.useSyncExternalStore(
t.subscribe,
() => e(t.getState()),
() => e(t.getInitialState())
);
return d.useDebugValue(r), r;
}
const l = (t) => {
const e = W(t), r = (o) => O(e, o);
return Object.assign(r, e), r;
}, I = (t) => t ? l(t) : l;
async function f(t, e = {}, r) {
return window.__TAURI_INTERNALS__.invoke(t, e, r);
}
const g = async () => {
try {
return await f("storyteller_get_credits_command");
} catch (t) {
throw t;
}
};
var _;
(function(t) {
t.WINDOW_RESIZED = "tauri://resize", t.WINDOW_MOVED = "tauri://move", t.WINDOW_CLOSE_REQUESTED = "tauri://close-requested", t.WINDOW_DESTROYED = "tauri://destroyed", t.WINDOW_FOCUS = "tauri://focus", t.WINDOW_BLUR = "tauri://blur", t.WINDOW_SCALE_FACTOR_CHANGED = "tauri://scale-change", t.WINDOW_THEME_CHANGED = "tauri://theme-changed", t.WINDOW_CREATED = "tauri://window-created", t.WEBVIEW_CREATED = "tauri://webview-created", t.DRAG_ENTER = "tauri://drag-enter", t.DRAG_OVER = "tauri://drag-over", t.DRAG_DROP = "tauri://drag-drop", t.DRAG_LEAVE = "tauri://drag-leave";
})(_ || (_ = {}));
const m = I((t) => ({
freeCredits: 0,
monthlyCredits: 0,
bankedCredits: 0,
totalCredits: 0,
// Call to fetch credits from the server
fetchFromServer: async () => {
let e;
try {
e = await g();
} catch (r) {
console.error("Error fetching credits", r);
return;
}
console.log("Fetched credits from server: ", e), e.payload && t((r) => ({
freeCredits: e.payload.free_credits,
monthlyCredits: e.payload.monthly_credits,
bankedCredits: e.payload.banked_credits,
totalCredits: e.payload.sum_total_credits
}));
}
}));
export {
m as useCreditsState
};
@@ -0,0 +1,11 @@
export interface CreditsState {
freeCredits: number;
monthlyCredits: number;
bankedCredits: number;
totalCredits: number;
}
export type CreditsActions = {
fetchFromServer: () => Promise<void>;
};
export declare const useCreditsState: import('zustand').UseBoundStore<import('zustand').StoreApi<CreditsState & CreditsActions>>;
//# sourceMappingURL=credits-state.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"credits-state.d.ts","sourceRoot":"","sources":["../../../src/lib/state/credits-state.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAE3B,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE,MAAM,CAAC;IAGvB,aAAa,EAAE,MAAM,CAAC;IAGtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,cAAc,GAAG;IAE3B,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC,CAAA;AAED,eAAO,MAAM,eAAe,4FAyBzB,CAAA"}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@storyteller/credits",
"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/state/credits-state';
@@ -0,0 +1,48 @@
import { create } from 'zustand'
import { ArtcraftGetCredits } from "@storyteller/tauri-api";
export interface CreditsState {
// Daily free credits (if/when we offer them)
freeCredits: number,
// Credits refilled monthly with a subscription
monthlyCredits: number,
// Credits the user purchases individually
bankedCredits: number,
// Total credits available
totalCredits: number,
}
export type CreditsActions = {
// Call to fetch credits from the server
fetchFromServer: () => Promise<void>
}
export const useCreditsState = create<CreditsState & CreditsActions>((set) => ({
freeCredits: 0,
monthlyCredits: 0,
bankedCredits: 0,
totalCredits: 0,
// Call to fetch credits from the server
fetchFromServer: async () => {
let data;
try {
data = await ArtcraftGetCredits();
} catch (error) {
console.error("Error fetching credits", error);
return;
}
console.log("Fetched credits from server: ", data);
if (!!data.payload) {
set((state) => ({
freeCredits: data.payload.free_credits,
monthlyCredits: data.payload.monthly_credits,
bankedCredits: data.payload.banked_credits,
totalCredits: data.payload.sum_total_credits,
}));
}
}
}))
+16
View File
@@ -0,0 +1,16 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../../tauri-api"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,50 @@
{
"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"
}
]
}
@@ -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"
}
]
}
@@ -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/state/credits',
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/credits',
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,
}
},
}));
@@ -0,0 +1,50 @@
{
"extends": [
"../../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.json"
],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": [
"{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}",
"{projectRoot}/vite.config.{js,ts,mjs,mts}"
]
}
]
}
}
]
}
@@ -0,0 +1,11 @@
# subscription
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build subscription` to build the library.
## Running unit tests
Run `nx test subscription` to execute the unit tests via [Vitest](https://vitest.dev/).
+4
View File
@@ -0,0 +1,4 @@
export * from './lib/subscription-plan-details';
export * from './lib/subscription-plans';
export * from './lib/subscription-state';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC"}
+147
View File
@@ -0,0 +1,147 @@
import u from "react";
const g = {
slug: "free",
name: "Free",
isPaidPlan: !1,
monthlyPrice: 0,
yearlyPrice: 0,
features: [
{ text: "Free daily generations", included: !0 },
{ text: "Limited access to ArtCraft tools", included: !0 }
],
colorScheme: "dark"
}, y = [
g,
{
slug: "artcraft_basic",
name: "Basic",
isPaidPlan: !0,
monthlyPrice: 8,
yearlyPrice: 96,
originalMonthlyPrice: 10,
originalYearlyPrice: 120,
features: [
{ text: "~1,010 Flux images", included: !0 },
{ text: "~36,000 real-time images", included: !0 },
{ text: "~180 enhanced images", included: !0 },
{ text: "~6 training jobs", included: !0 },
{ text: "Commercial license", included: !0 }
],
colorScheme: "green"
},
{
slug: "artcraft_pro",
name: "Pro",
isPaidPlan: !0,
monthlyPrice: 28,
yearlyPrice: 336,
originalMonthlyPrice: 35,
originalYearlyPrice: 420,
features: [
{ text: "~5,048 Flux images", included: !0 },
{ text: "~180,000 real-time images", included: !0 },
{ text: "~900 enhanced images", included: !0 },
{ text: "~30 training jobs", included: !0 },
{ text: "Commercial license", included: !0 }
],
colorScheme: "purple"
},
{
slug: "artcraft_max",
name: "Max",
isPaidPlan: !0,
monthlyPrice: 48,
yearlyPrice: 576,
originalMonthlyPrice: 60,
originalYearlyPrice: 720,
features: [
{ text: "~15,142 Flux images", included: !0 },
{ text: "~540,000 real-time images", included: !0 },
{ text: "~2,700 enhanced images", included: !0 },
{ text: "~90 training jobs", included: !0 },
{ text: "Commercial license", included: !0 }
],
colorScheme: "orange"
}
], O = new Map(
y.map((e) => [e.slug, e])
), l = (e) => {
let i;
const t = /* @__PURE__ */ new Set(), r = (n, s) => {
const a = typeof n == "function" ? n(i) : n;
if (!Object.is(a, i)) {
const m = i;
i = s ?? (typeof a != "object" || a === null) ? a : Object.assign({}, i, a), t.forEach((b) => b(i, m));
}
}, c = () => i, o = { setState: r, getState: c, getInitialState: () => p, subscribe: (n) => (t.add(n), () => t.delete(n)) }, p = i = e(r, c, o);
return o;
}, S = (e) => e ? l(e) : l, E = (e) => e;
function P(e, i = E) {
const t = u.useSyncExternalStore(
e.subscribe,
() => i(e.getState()),
() => i(e.getInitialState())
);
return u.useDebugValue(t), t;
}
const d = (e) => {
const i = S(e), t = (r) => P(i, r);
return Object.assign(t, i), t;
}, x = (e) => e ? d(e) : d;
async function f(e, i = {}, t) {
return window.__TAURI_INTERNALS__.invoke(e, i, t);
}
const D = async () => {
try {
return await f("storyteller_get_subscription_command");
} catch (e) {
throw e;
}
};
var _;
(function(e) {
e.WINDOW_RESIZED = "tauri://resize", e.WINDOW_MOVED = "tauri://move", e.WINDOW_CLOSE_REQUESTED = "tauri://close-requested", e.WINDOW_DESTROYED = "tauri://destroyed", e.WINDOW_FOCUS = "tauri://focus", e.WINDOW_BLUR = "tauri://blur", e.WINDOW_SCALE_FACTOR_CHANGED = "tauri://scale-change", e.WINDOW_THEME_CHANGED = "tauri://theme-changed", e.WINDOW_CREATED = "tauri://window-created", e.WEBVIEW_CREATED = "tauri://webview-created", e.DRAG_ENTER = "tauri://drag-enter", e.DRAG_OVER = "tauri://drag-over", e.DRAG_DROP = "tauri://drag-drop", e.DRAG_LEAVE = "tauri://drag-leave";
})(_ || (_ = {}));
const A = x((e, i) => ({
subscriptionInfo: void 0,
// Returns true if the user has a paid plan.
// (We do not store "free" as a plan.)
hasPaidPlan: () => {
var t;
return ((t = i().subscriptionInfo) == null ? void 0 : t.subscriptionToken) !== void 0;
},
// Returns true if the user can cancel their plan.
// (The user can't cancel an already set to cancel/expire plan again)
canCancelPlan: () => {
const t = i().subscriptionInfo;
return (t == null ? void 0 : t.subscriptionToken) !== void 0 && (t == null ? void 0 : t.subscriptionEndAt) === void 0;
},
// Call to fetch credits from the server
fetchFromServer: async () => {
let t;
try {
t = await D();
} catch (r) {
console.error("Error fetching subscription", r);
return;
}
if (console.log("Fetched subscription from server: ", t), t.payload) {
let r;
t.payload.active_subscription && (r = {
subscriptionToken: t.payload.active_subscription.subscription_token,
productSlug: t.payload.active_subscription.product_slug,
namespace: t.payload.active_subscription.namespace,
nextBillAt: t.payload.active_subscription.next_bill_at ? new Date(t.payload.active_subscription.next_bill_at) : void 0,
subscriptionEndAt: t.payload.active_subscription.subscription_end_at ? new Date(t.payload.active_subscription.subscription_end_at) : void 0
}), e((c) => ({
subscriptionInfo: r
}));
}
}
}));
export {
g as FREE_PLAN,
y as SUBSCRIPTION_PLANS,
O as SUBSCRIPTION_PLANS_BY_SLUG,
A as useSubscriptionState
};
@@ -0,0 +1,18 @@
export interface PricingFeature {
text: string;
included: boolean;
tooltip?: string;
}
export interface SubscriptionPlanDetails {
slug: string;
name: string;
isPaidPlan: boolean;
monthlyPrice: number;
yearlyPrice: number;
originalMonthlyPrice?: number;
originalYearlyPrice?: number;
features: PricingFeature[];
isPopular?: boolean;
colorScheme: "dark" | "green" | "purple" | "orange";
}
//# sourceMappingURL=subscription-plan-details.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"subscription-plan-details.d.ts","sourceRoot":"","sources":["../../src/lib/subscription-plan-details.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAEb,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IAEpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAK7B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACrD"}
@@ -0,0 +1,5 @@
import { SubscriptionPlanDetails } from './subscription-plan-details.js';
export declare const FREE_PLAN: SubscriptionPlanDetails;
export declare const SUBSCRIPTION_PLANS: SubscriptionPlanDetails[];
export declare const SUBSCRIPTION_PLANS_BY_SLUG: Map<string, SubscriptionPlanDetails>;
//# sourceMappingURL=subscription-plans.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"subscription-plans.d.ts","sourceRoot":"","sources":["../../src/lib/subscription-plans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,eAAO,MAAM,SAAS,EAAG,uBAWxB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAG,uBAAuB,EAqDxD,CAAA;AAED,eAAO,MAAM,0BAA0B,EAAG,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAE5E,CAAC"}
@@ -0,0 +1,18 @@
export interface SubscriptionState {
subscriptionInfo?: SubscriptionInfo;
}
interface SubscriptionInfo {
subscriptionToken: string;
productSlug: string;
namespace: string;
nextBillAt?: Date;
subscriptionEndAt?: Date;
}
export type SubscriptionActions = {
hasPaidPlan: () => boolean;
canCancelPlan: () => boolean;
fetchFromServer: () => Promise<void>;
};
export declare const useSubscriptionState: import('zustand').UseBoundStore<import('zustand').StoreApi<SubscriptionState & SubscriptionActions>>;
export {};
//# sourceMappingURL=subscription-state.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"subscription-state.d.ts","sourceRoot":"","sources":["../../src/lib/subscription-state.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAEhC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,UAAU,gBAAgB;IAExB,iBAAiB,EAAE,MAAM,CAAC;IAI1B,WAAW,EAAE,MAAM,CAAC;IAGpB,SAAS,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,IAAI,CAAC;IAGlB,iBAAiB,CAAC,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG;IAGhC,WAAW,EAAE,MAAM,OAAO,CAAC;IAI3B,aAAa,EAAE,MAAM,OAAO,CAAC;IAG7B,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC,CAAC;AAEF,eAAO,MAAM,oBAAoB,sGAyC9B,CAAA"}
@@ -0,0 +1,18 @@
{
"name": "@storyteller/subscription",
"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"
}
},
"dependencies": {}
}
@@ -0,0 +1,4 @@
export * from './lib/subscription-plan-details';
export * from './lib/subscription-plans';
export * from './lib/subscription-state';
@@ -0,0 +1,24 @@
export interface PricingFeature {
text: string;
included: boolean;
tooltip?: string;
}
export interface SubscriptionPlanDetails {
slug: string;
name: string;
isPaidPlan: boolean;
monthlyPrice: number;
yearlyPrice: number;
originalMonthlyPrice?: number;
originalYearlyPrice?: number;
//monthlyPriceId?: string;
//yearlyPriceId?: string;
features: PricingFeature[];
isPopular?: boolean;
colorScheme: "dark" | "green" | "purple" | "orange";
}
@@ -0,0 +1,73 @@
import { SubscriptionPlanDetails } from "./subscription-plan-details.js";
export const FREE_PLAN : SubscriptionPlanDetails = {
slug: "free",
name: "Free",
isPaidPlan: false,
monthlyPrice: 0,
yearlyPrice: 0,
features: [
{ text: "Free daily generations", included: true },
{ text: "Limited access to ArtCraft tools", included: true },
],
colorScheme: "dark" as const,
};
export const SUBSCRIPTION_PLANS : SubscriptionPlanDetails[] = [
FREE_PLAN,
{
slug: "artcraft_basic",
name: "Basic",
isPaidPlan: true,
monthlyPrice: 8,
yearlyPrice: 96,
originalMonthlyPrice: 10,
originalYearlyPrice: 120,
features: [
{ text: "~1,010 Flux images", included: true },
{ text: "~36,000 real-time images", included: true },
{ text: "~180 enhanced images", included: true },
{ text: "~6 training jobs", included: true },
{ text: "Commercial license", included: true },
],
colorScheme: "green" as const,
},
{
slug: "artcraft_pro",
name: "Pro",
isPaidPlan: true,
monthlyPrice: 28,
yearlyPrice: 336,
originalMonthlyPrice: 35,
originalYearlyPrice: 420,
features: [
{ text: "~5,048 Flux images", included: true },
{ text: "~180,000 real-time images", included: true },
{ text: "~900 enhanced images", included: true },
{ text: "~30 training jobs", included: true },
{ text: "Commercial license", included: true },
],
colorScheme: "purple" as const,
},
{
slug: "artcraft_max",
name: "Max",
isPaidPlan: true,
monthlyPrice: 48,
yearlyPrice: 576,
originalMonthlyPrice: 60,
originalYearlyPrice: 720,
features: [
{ text: "~15,142 Flux images", included: true },
{ text: "~540,000 real-time images", included: true },
{ text: "~2,700 enhanced images", included: true },
{ text: "~90 training jobs", included: true },
{ text: "Commercial license", included: true },
],
colorScheme: "orange" as const,
},
]
export const SUBSCRIPTION_PLANS_BY_SLUG : Map<string, SubscriptionPlanDetails> = new Map(
SUBSCRIPTION_PLANS.map((plan) => [plan.slug, plan])
);
@@ -0,0 +1,81 @@
import { create } from 'zustand'
import { ArtcraftGetSubscription } from "@storyteller/tauri-api";
export interface SubscriptionState {
/// If the user doesn't have a subscription, this will be undefined.
subscriptionInfo?: SubscriptionInfo,
}
interface SubscriptionInfo {
/// The internal user unique ID for the subscription
subscriptionToken: string,
// TODO: This should be an enum!
/// The internal identifier of the subscription
productSlug: string,
/// The namespace for the subscription ("artcraft" or "fakeyou")
namespace: string,
/// The next billing date for the subscription (if it's active and not set to expire/cancel)
nextBillAt?: Date,
/// The end date for the subscription (if it's expired or set to expire/cancel)
subscriptionEndAt?: Date,
}
export type SubscriptionActions = {
// Returns true if the user has a paid plan.
// (We do not store "free" as a plan.)
hasPaidPlan: () => boolean;
// Returns true if the user can cancel their plan.
// (The user can't cancel an already set to cancel/expire plan again)
canCancelPlan: () => boolean;
// Call to fetch credits from the server
fetchFromServer: () => Promise<void>
};
export const useSubscriptionState = create<SubscriptionState & SubscriptionActions>((set, get) => ({
subscriptionInfo: undefined,
// Returns true if the user has a paid plan.
// (We do not store "free" as a plan.)
hasPaidPlan: () => get().subscriptionInfo?.subscriptionToken !== undefined,
// Returns true if the user can cancel their plan.
// (The user can't cancel an already set to cancel/expire plan again)
canCancelPlan: () => {
const info = get().subscriptionInfo;
return info?.subscriptionToken !== undefined
&& info?.subscriptionEndAt === undefined;
},
// Call to fetch credits from the server
fetchFromServer: async () => {
let data;
try {
data = await ArtcraftGetSubscription();
} catch (error) {
console.error("Error fetching subscription", error);
return;
}
console.log("Fetched subscription from server: ", data);
if (!!data.payload) {
let activeSubscription = undefined;
if (!!data.payload.active_subscription) {
activeSubscription = {
subscriptionToken: data.payload.active_subscription.subscription_token,
productSlug: data.payload.active_subscription.product_slug,
namespace: data.payload.active_subscription.namespace,
nextBillAt: data.payload.active_subscription.next_bill_at ? new Date(data.payload.active_subscription.next_bill_at) : undefined,
subscriptionEndAt: data.payload.active_subscription.subscription_end_at ? new Date(data.payload.active_subscription.subscription_end_at) : undefined,
};
}
set((state) => ({
subscriptionInfo: activeSubscription,
}));
}
}
}))
@@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "../../tauri-api"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
@@ -0,0 +1,39 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
"emitDeclarationOnly": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"forceConsistentCasingInFileNames": true,
"types": [
"node",
"vite/client"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"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"
],
"references": [
{
"path": "../../tauri-api/tsconfig.lib.json"
}
]
}
@@ -0,0 +1,50 @@
{
"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"
}
]
}
@@ -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/state/subscription',
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/subscription',
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: 'node',
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,
}
},
}));