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 f5c39d3bd9..483abc4353 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx @@ -6,6 +6,8 @@ import { faFilm, faPaintbrush, faImage, + faDiamond, + faGem, } from "@fortawesome/pro-solid-svg-icons"; import { Button } from "@storyteller/ui-button"; import { AuthButtons } from "./AuthButtons"; @@ -34,6 +36,7 @@ import { is3DSceneLoaded, set3DPageMounted, } from "~/pages/PageEnigma/Editor/editor"; +import { usePricingModalStore } from "@storyteller/ui-pricing-modal"; interface Props { pageName: string; } @@ -92,6 +95,8 @@ export const TopBar = ({ pageName }: Props) => { ); }; + const { toggleModal } = usePricingModalStore(); + return ( <>
@@ -140,6 +145,14 @@ export const TopBar = ({ pageName }: Props) => {
+
+
+ ); + })} +
+ + {/* Manage Subscription Button - only show if user has a paid plan */} + {hasActiveSub && activePlanId !== "free" && ( +
+ +
+ )} + + + ); +} + +// Additional interfaces for Stripe integration +export interface SubscriptionData { + currentPlanId: string; + hasActiveSubscription: boolean; + customerId?: string; + subscriptionId?: string; + billingCycle?: "monthly" | "yearly"; + nextBillingDate?: Date; +} + +export default PricingModal; diff --git a/frontend/libs/components/pricing-modal/tsconfig.json b/frontend/libs/components/pricing-modal/tsconfig.json new file mode 100644 index 0000000000..09a38a583b --- /dev/null +++ b/frontend/libs/components/pricing-modal/tsconfig.json @@ -0,0 +1,19 @@ +{ + "files": [], + "include": [], + "references": [ + { + "path": "../button" + }, + { + "path": "../modal" + }, + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json" +} diff --git a/frontend/libs/components/pricing-modal/tsconfig.lib.json b/frontend/libs/components/pricing-modal/tsconfig.lib.json new file mode 100644 index 0000000000..5e02a0804f --- /dev/null +++ b/frontend/libs/components/pricing-modal/tsconfig.lib.json @@ -0,0 +1,53 @@ +{ + "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": "../button/tsconfig.lib.json" + }, + { + "path": "../modal/tsconfig.lib.json" + } + ] +} diff --git a/frontend/libs/components/pricing-modal/tsconfig.spec.json b/frontend/libs/components/pricing-modal/tsconfig.spec.json new file mode 100644 index 0000000000..18b7de51b8 --- /dev/null +++ b/frontend/libs/components/pricing-modal/tsconfig.spec.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" + } + ] +} diff --git a/frontend/libs/components/pricing-modal/vite.config.ts b/frontend/libs/components/pricing-modal/vite.config.ts new file mode 100644 index 0000000000..2f749fcc1b --- /dev/null +++ b/frontend/libs/components/pricing-modal/vite.config.ts @@ -0,0 +1,49 @@ +/// +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/components/pricing-modal', + 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: '@storyteller/ui-pricing-modal', + 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, + } + }, +})); diff --git a/frontend/libs/components/tab-selector/src/lib/tab-selector.tsx b/frontend/libs/components/tab-selector/src/lib/tab-selector.tsx index d0c46b9f1e..68f96ac0c6 100644 --- a/frontend/libs/components/tab-selector/src/lib/tab-selector.tsx +++ b/frontend/libs/components/tab-selector/src/lib/tab-selector.tsx @@ -15,6 +15,8 @@ interface TabSelectorProps { className?: string; disabled?: boolean; disabledMessage?: string; + tabClassName?: string; + selectedTabClassName?: string; } export const TabSelector: React.FC = ({ @@ -24,6 +26,8 @@ export const TabSelector: React.FC = ({ className, disabled, disabledMessage, + tabClassName, + selectedTabClassName, }) => { // Find the index of the active tab const selectedIndex = tabs.findIndex((tab) => tab.id === activeTab); @@ -83,8 +87,11 @@ export const TabSelector: React.FC = ({ twMerge( "relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out", "focus-visible:outline-none focus-visible:ring-0", - selected ? "text-white" : "text-gray-300 hover:text-white", - disabled ? "cursor-not-allowed opacity-60" : "" + selected + ? twMerge("text-white", selectedTabClassName) + : "text-gray-300 hover:text-white", + disabled ? "cursor-not-allowed opacity-60" : "", + tabClassName ) } > diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bb7a6679b5..592df2538e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1466,6 +1466,10 @@ "name": "@storyteller/ui-popover", "version": "0.0.1" }, + "libs/components/pricing-modal": { + "name": "@storyteller/ui-pricing-modal", + "version": "0.0.1" + }, "libs/components/promptbox": { "name": "@storyteller/ui-promptbox", "version": "0.0.1" @@ -9765,6 +9769,10 @@ "resolved": "libs/components/popover", "link": true }, + "node_modules/@storyteller/ui-pricing-modal": { + "resolved": "libs/components/pricing-modal", + "link": true + }, "node_modules/@storyteller/ui-promptbox": { "resolved": "libs/components/promptbox", "link": true diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index aad900be48..702005be65 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -140,6 +140,9 @@ }, { "path": "./libs/components/image-input" + }, + { + "path": "./libs/components/pricing-modal" } ] }