mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-25 10:39:37 +00:00
credits ui - topbar and settings modal (#804)
- new progress bar and progress circle component - clicking the credits in the top opens the settings modal on the "Plans & Credits" tab
This commit is contained in:
@@ -35,7 +35,6 @@ import {
|
||||
import { signal } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { setLogoutStates } from "~/signals/authentication/utilities";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { TabId, useTabStore } from "~/pages/Stores/TabState";
|
||||
import {
|
||||
@@ -43,7 +42,6 @@ import {
|
||||
is3DSceneLoaded,
|
||||
set3DPageMounted,
|
||||
} from "~/pages/PageEnigma/Editor/editor";
|
||||
// import { usePricingModalStore } from "@storyteller/ui-pricing-modal"; - Uncomment for pricing modal - BFlat
|
||||
import toast from "react-hot-toast";
|
||||
import { gtagEvent } from "@storyteller/google-analytics";
|
||||
import {
|
||||
@@ -55,12 +53,22 @@ import { BaseSelectorImage } from "../../../pages/PageEdit/BaseImageSelector";
|
||||
import { ProviderSetupModal } from "@storyteller/provider-setup-modal";
|
||||
import { ProviderBillingModal } from "@storyteller/provider-billing-modal";
|
||||
import { usePricingModalStore } from "@storyteller/ui-pricing-modal";
|
||||
// import { ProgressCircle } from "@storyteller/ui-progress"; // Uncomment this for Credits indicator - BFlat
|
||||
|
||||
interface Props {
|
||||
pageName: string;
|
||||
loginSignUpPressed: () => void;
|
||||
}
|
||||
|
||||
// Settings section type to match the SettingsModal component
|
||||
type SettingsSection =
|
||||
| "general"
|
||||
| "accounts"
|
||||
| "alerts"
|
||||
| "about"
|
||||
| "provider_priority"
|
||||
| "billing";
|
||||
|
||||
const SWITCHER_THROTTLE_TIME = 500; // milliseconds
|
||||
|
||||
// NB: See `TabState` for the default tab.
|
||||
@@ -108,6 +116,18 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
useSignals();
|
||||
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
const [settingsSection, setSettingsSection] =
|
||||
useState<SettingsSection>("general");
|
||||
|
||||
// Credit state (replace with real data fetch)
|
||||
const [credits, setCredits] = useState({
|
||||
used: 800,
|
||||
total: 1000,
|
||||
});
|
||||
|
||||
const creditsRemaining = credits.total - credits.used;
|
||||
const creditsRemainingPercentage = (creditsRemaining / credits.total) * 100;
|
||||
const isLowCredit = creditsRemainingPercentage < 20; // Less than 20% credits remaining
|
||||
|
||||
const { isDesktop, isMaximized, minimize, toggleMaximize, close } =
|
||||
useTauriWindowControls();
|
||||
@@ -119,6 +139,16 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
gtagEvent("open_gallery_modal", { tab: tabStore.activeTabId });
|
||||
};
|
||||
|
||||
// Force recreation of the modal when switching to billing
|
||||
const handleOpenBillingSettings = () => {
|
||||
setIsSettingsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setSettingsSection("billing");
|
||||
setIsSettingsModalOpen(true);
|
||||
gtagEvent("open_billing_settings");
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const is3DSceneReady = is3DSceneLoaded.value;
|
||||
@@ -263,33 +293,60 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2" data-tauri-drag-region>
|
||||
<div className="no-drag flex gap-2">
|
||||
{/* - Uncomment for pricing modal - BFlat */}
|
||||
<div className="no-drag flex items-center gap-1.5">
|
||||
{/* Uncomment this for Credits indicator - BFlat */}
|
||||
{/* <Tooltip
|
||||
content={`${creditsRemaining}/${credits.total} credits remaining`}
|
||||
position="bottom"
|
||||
delay={300}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-[30px] px-2 ps-1.5"
|
||||
onClick={handleOpenBillingSettings}
|
||||
>
|
||||
<ProgressCircle
|
||||
value={creditsRemainingPercentage}
|
||||
isLow={isLowCredit}
|
||||
size="small"
|
||||
/>
|
||||
<span className="whitespace-nowrap text-sm font-medium">
|
||||
{creditsRemaining} Credits
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faGem}
|
||||
onClick={toggleModal}
|
||||
className="shadow-md shadow-primary-500/50 transition-all duration-300 hover:shadow-md hover:shadow-primary-500/75"
|
||||
className="h-[38px] shadow-md shadow-primary-500/50 transition-all duration-300 hover:shadow-md hover:shadow-primary-500/75"
|
||||
>
|
||||
Upgrade Now
|
||||
</Button>
|
||||
|
||||
<Tooltip content="Settings" position="bottom" delay={300}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={faGear}
|
||||
className="h-[38px] w-[38px]"
|
||||
onClick={() => {
|
||||
setSettingsSection("general");
|
||||
setIsSettingsModalOpen(true);
|
||||
gtagEvent("open_settings_modal");
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-[38px]"
|
||||
icon={faImages}
|
||||
onClick={handleOpenGalleryModal}
|
||||
>
|
||||
My Library
|
||||
<span className="hidden whitespace-nowrap xl:block">
|
||||
My Library
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<Activity />
|
||||
@@ -333,6 +390,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
isOpen={isSettingsModalOpen}
|
||||
onClose={() => setIsSettingsModalOpen(false)}
|
||||
globalAccountLogoutCallback={() => setLogoutStates()}
|
||||
initialSection={settingsSection}
|
||||
/>
|
||||
|
||||
<GalleryModal
|
||||
@@ -342,7 +400,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
/>
|
||||
|
||||
<ProviderSetupModal />
|
||||
<ProviderBillingModal />
|
||||
<ProviderBillingModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
iconClassName?: string;
|
||||
iconFlip?: boolean;
|
||||
htmlFor?: string;
|
||||
variant?: "primary" | "secondary" | "action" | "destructive";
|
||||
variant?: "primary" | "secondary" | "action" | "destructive" | "ghost";
|
||||
loading?: boolean;
|
||||
as?: "button" | "link";
|
||||
href?: string;
|
||||
@@ -47,6 +47,9 @@ export const Button = ({
|
||||
case "destructive": {
|
||||
return "bg-red/70 hover:bg-red/50 text-white focus-visible:outline-red";
|
||||
}
|
||||
case "ghost": {
|
||||
return "bg-transparent hover:bg-white/[8%] text-white focus-visible:outline-primary-600";
|
||||
}
|
||||
case "primary":
|
||||
default: {
|
||||
return "bg-primary hover:bg-primary-400 text-white focus-visible:outline-primary-600";
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# @storyteller/ui-progress
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test @storyteller/ui-progress` to execute the unit tests via [Vitest](https://vitest.dev/).
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@storyteller/ui-progress",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./lib/progress";
|
||||
export * from "./lib/progress-circle";
|
||||
@@ -0,0 +1,104 @@
|
||||
import { HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ProgressCircleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
value?: number;
|
||||
max?: number;
|
||||
isLow?: boolean;
|
||||
size?: "small" | "large";
|
||||
showPercentage?: boolean;
|
||||
reverse?: boolean;
|
||||
}
|
||||
|
||||
export const ProgressCircle = ({
|
||||
value = 0,
|
||||
max = 100,
|
||||
isLow = false,
|
||||
size = "large",
|
||||
showPercentage = false,
|
||||
reverse = false,
|
||||
className,
|
||||
...props
|
||||
}: ProgressCircleProps) => {
|
||||
const percentage = Math.min(Math.max(0, value), max);
|
||||
const displayPercentage = percentage;
|
||||
|
||||
// Make small circles actually smaller
|
||||
const radius = size === "small" ? 8 : 40;
|
||||
const strokeWidth = size === "small" ? 4 : 6;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
// Calculate stroke dashoffset differently based on direction
|
||||
const strokeDashoffset = reverse
|
||||
? (percentage / 100) * circumference // Counter-clockwise
|
||||
: circumference - (percentage / 100) * circumference; // Clockwise
|
||||
|
||||
const viewBoxSize = (radius + strokeWidth) * 2;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative inline-flex items-center justify-center",
|
||||
size === "small" ? "h-5 w-5" : "h-24 w-24",
|
||||
showPercentage && size === "small" ? "mr-6" : "", // Add margin for small with label
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{/* For small size, show percentage label outside to the right */}
|
||||
{showPercentage && size === "small" && (
|
||||
<div className="absolute -right-6 flex items-center justify-center">
|
||||
<span className="text-xs font-medium">
|
||||
{Math.round(displayPercentage)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<svg
|
||||
className={`absolute h-full w-full ${
|
||||
reverse ? "rotate-90" : "-rotate-90"
|
||||
}`}
|
||||
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
||||
>
|
||||
<circle
|
||||
className="stroke-white/20"
|
||||
cx={viewBoxSize / 2}
|
||||
cy={viewBoxSize / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<circle
|
||||
className={twMerge(
|
||||
"transition-all",
|
||||
isLow ? "stroke-red/80" : "stroke-primary"
|
||||
)}
|
||||
cx={viewBoxSize / 2}
|
||||
cy={viewBoxSize / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
transform={
|
||||
reverse
|
||||
? `rotate(180, ${viewBoxSize / 2}, ${viewBoxSize / 2})`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* For large size, show percentage in the center */}
|
||||
{showPercentage && size === "large" && (
|
||||
<div className="absolute flex flex-col items-center justify-center text-center">
|
||||
<span className="text-xl font-medium">
|
||||
{Math.round(displayPercentage)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressCircle;
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Replace this with your own classes
|
||||
*
|
||||
* e.g.
|
||||
* .container {
|
||||
* }
|
||||
*/
|
||||
@@ -0,0 +1,12 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import StorytellerUiProgress from './progress';
|
||||
|
||||
describe('StorytellerUiProgress', () => {
|
||||
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<StorytellerUiProgress />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
|
||||
value?: number;
|
||||
max?: number;
|
||||
isLow?: boolean;
|
||||
}
|
||||
|
||||
export const Progress = ({
|
||||
value = 0,
|
||||
max = 100,
|
||||
isLow = false,
|
||||
className,
|
||||
...props
|
||||
}: ProgressProps) => {
|
||||
const percentage = Math.min(Math.max(0, value), max);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"h-full w-full flex-1 transition-all",
|
||||
isLow ? "bg-red/60" : "bg-primary"
|
||||
)}
|
||||
style={{ transform: `translateX(-${100 - percentage}%)` }}
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={percentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Progress;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -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/components/progress',
|
||||
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-progress',
|
||||
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,
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -176,7 +176,7 @@ export const PromptBoxEdit = ({
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe your image..."
|
||||
placeholder="Write what you want to change in your image and click generate..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { Progress } from "@storyteller/ui-progress";
|
||||
import {
|
||||
faCircleDollar,
|
||||
faInfoCircle,
|
||||
faStar,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBillingData = async () => {
|
||||
// TODO: Replace with actual API call - BFlat
|
||||
// const data = await GetBillingInfo();
|
||||
// setBillingInfo(data.payload);
|
||||
};
|
||||
fetchBillingData();
|
||||
}, []);
|
||||
|
||||
const creditPercentage =
|
||||
(billingInfo.credits.remaining / billingInfo.credits.total) * 100;
|
||||
const isLowCredit = creditPercentage < 20; // If used more than 80% of credits, consider it low
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col">
|
||||
<Label htmlFor="credits" className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCircleDollar} />
|
||||
Monthly credits left
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress
|
||||
value={creditPercentage}
|
||||
isLow={isLowCredit}
|
||||
className="h-2 w-full"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground font-medium">
|
||||
{billingInfo.credits.remaining}/{billingInfo.credits.total}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-white/10" />
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label>Current Plan</Label>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faStar} className="text-purple/70" />
|
||||
{billingInfo.plan}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" className="h-[30px]">
|
||||
Cancel plan
|
||||
</Button>
|
||||
<Button variant="primary" className="h-[30px]">
|
||||
Change plan
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-white/50">
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
Next {billingInfo.nextPayment.amount} payment due{" "}
|
||||
{billingInfo.nextPayment.date}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faUser,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
faVolumeHigh,
|
||||
faCircleInfo,
|
||||
faRoute,
|
||||
faCreditCard,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { MiscSettingsPane } from "./panes/MiscSettingsPane";
|
||||
@@ -15,6 +16,7 @@ import { AccountSettingsPane } from "./panes/AccountSettings/AccountSettingsPane
|
||||
import { AboutSettingsPane } from "./panes/AboutSettingsPane";
|
||||
import { ProviderPrioritySettingsPane } from "./panes/ProviderPrioritySettingsPane";
|
||||
import { gtagEvent } from "@storyteller/google-analytics";
|
||||
import { BillingSettingsPane } from "./panes/BillingSettingsPane";
|
||||
|
||||
interface SettingsModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -28,7 +30,8 @@ type SettingsSection =
|
||||
| "accounts"
|
||||
| "alerts"
|
||||
| "about"
|
||||
| "provider_priority";
|
||||
| "provider_priority"
|
||||
| "billing";
|
||||
|
||||
export const SettingsModal = ({
|
||||
isOpen,
|
||||
@@ -39,9 +42,17 @@ export const SettingsModal = ({
|
||||
const [selectedSection, setSelectedSection] =
|
||||
useState<SettingsSection>(initialSection);
|
||||
|
||||
// Sync the selected section with incoming prop when modal opens or prop changes
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setSelectedSection(initialSection);
|
||||
}
|
||||
}, [isOpen, initialSection]);
|
||||
|
||||
const sections = [
|
||||
{ id: "general" as const, label: "General", icon: faCog },
|
||||
{ id: "accounts" as const, label: "Accounts", icon: faUser },
|
||||
// { id: "billing" as const, label: "Plan & Credits", icon: faCreditCard }, // Uncomment this for billing - BFlat
|
||||
{
|
||||
id: "provider_priority" as const,
|
||||
label: "Provider Priority",
|
||||
@@ -69,6 +80,8 @@ export const SettingsModal = ({
|
||||
return <AboutSettingsPane />;
|
||||
case "provider_priority":
|
||||
return <ProviderPrioritySettingsPane />;
|
||||
case "billing":
|
||||
return <BillingSettingsPane />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,7 +108,9 @@ export const SettingsModal = ({
|
||||
section.id === selectedSection ? "bg-[#63636B]/40" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
gtagEvent("switch_settings_section", { "section": section.id });
|
||||
gtagEvent("switch_settings_section", {
|
||||
section: section.id,
|
||||
});
|
||||
setSelectedSection(section.id);
|
||||
}}
|
||||
>
|
||||
|
||||
Generated
+8
-151
File diff suppressed because it is too large
Load Diff
@@ -156,6 +156,9 @@
|
||||
{
|
||||
"path": "./libs/components/provider-setup-modal"
|
||||
},
|
||||
{
|
||||
"path": "./libs/components/progress"
|
||||
},
|
||||
{
|
||||
"path": "./libs/components/provider-billing-modal"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user