├── src
├── hooks
│ ├── useDebounce.ts
│ ├── usePagePosition.tsx
│ ├── use-mobile.tsx
│ ├── useUser.tsx
│ ├── UserContext.tsx
│ └── useBreadcrumbs.ts
├── pages
│ ├── error-demo
│ │ └── ErrorDemo.tsx
│ ├── error
│ │ ├── index.ts
│ │ └── ErrorPage.tsx
│ ├── settings
│ │ └── index.ts
│ ├── home
│ │ └── index.ts
│ ├── webhooks
│ │ └── index.ts
│ ├── developer
│ │ └── index.ts
│ ├── onboarding
│ │ └── index.ts
│ ├── usage
│ │ └── index.ts
│ ├── customer
│ │ ├── creditnotes
│ │ │ └── CreditNoteDetailsPage.tsx
│ │ └── payments
│ │ │ ├── PaymentPage.tsx
│ │ │ └── PaymentList.tsx
│ ├── product-catalog
│ │ ├── addons
│ │ │ └── AddonCharges.tsx
│ │ ├── cost-sheets
│ │ │ └── CostSheetCharges.tsx
│ │ ├── plans
│ │ │ └── AddCharges.tsx
│ │ └── index.ts
│ ├── auth
│ │ └── index.ts
│ ├── index.ts
│ └── insights-tools
│ │ ├── index.ts
│ │ └── exports
│ │ └── TaskRunsPage.tsx
├── components
│ ├── atoms
│ │ ├── Chip
│ │ │ └── index.ts
│ │ ├── Page
│ │ │ ├── index.ts
│ │ │ └── Page.tsx
│ │ ├── Dialog
│ │ │ ├── index.ts
│ │ │ └── Dialog.tsx
│ │ ├── Divider
│ │ │ ├── index.ts
│ │ │ └── Divider.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ └── Input.stories.tsx
│ │ ├── Label
│ │ │ ├── index.ts
│ │ │ └── Label.tsx
│ │ ├── Modal
│ │ │ ├── index.ts
│ │ │ └── Modal.tsx
│ │ ├── Sheet
│ │ │ └── index.ts
│ │ ├── Spacer
│ │ │ ├── index.ts
│ │ │ └── Spacer.tsx
│ │ ├── Spinner
│ │ │ ├── index.ts
│ │ │ └── Spinner.tsx
│ │ ├── Stepper
│ │ │ └── index.ts
│ │ ├── Toggle
│ │ │ ├── index.ts
│ │ │ └── Toggle.tsx
│ │ ├── Tooltip
│ │ │ ├── index.ts
│ │ │ └── Tooltip.tsx
│ │ ├── Checkbox
│ │ │ └── index.ts
│ │ ├── CodeBlock
│ │ │ ├── index.ts
│ │ │ └── CodeBlock.tsx
│ │ ├── Progress
│ │ │ ├── index.ts
│ │ │ └── Progress.tsx
│ │ ├── Textarea
│ │ │ └── index.ts
│ │ ├── ActionButton
│ │ │ └── index.ts
│ │ ├── Card
│ │ │ └── index.ts
│ │ ├── CodePreview
│ │ │ └── index.ts
│ │ ├── ComingSoon
│ │ │ ├── index.ts
│ │ │ └── ComingSoon.tsx
│ │ ├── DatePicker
│ │ │ └── index.ts
│ │ ├── FormHeader
│ │ │ └── index.ts
│ │ ├── MultiSelect
│ │ │ └── index.ts
│ │ ├── NoDataCard
│ │ │ ├── index.ts
│ │ │ └── NoDataCard.tsx
│ │ ├── DateTimePicker
│ │ │ └── index.ts
│ │ ├── MultichipInput
│ │ │ └── index.ts
│ │ ├── SectionHeader
│ │ │ └── index.ts
│ │ ├── SelectFeature
│ │ │ └── index.ts
│ │ ├── DateRangePicker
│ │ │ └── index.ts
│ │ ├── DecimalUsageInput
│ │ │ └── index.ts
│ │ ├── ShortPagination
│ │ │ └── index.ts
│ │ ├── FeatureMultiSelect
│ │ │ └── index.ts
│ │ ├── PaymentUrlSuccessDialog
│ │ │ └── index.ts
│ │ ├── Loader
│ │ │ └── index.ts
│ │ ├── RadioGroup
│ │ │ └── index.ts
│ │ ├── Combobox
│ │ │ └── index.ts
│ │ ├── CheckboxRadioGroup
│ │ │ └── index.ts
│ │ ├── Button
│ │ │ ├── index.ts
│ │ │ └── AddButton.tsx
│ │ ├── ErrorBoundary
│ │ │ └── index.ts
│ │ └── Select
│ │ │ └── index.ts
│ ├── molecules
│ │ ├── DebugMenu
│ │ │ └── index.ts
│ │ ├── Events
│ │ │ └── index.ts
│ │ ├── Pagination
│ │ │ └── index.ts
│ │ ├── PlanDrawer
│ │ │ └── index.ts
│ │ ├── PlansTable
│ │ │ └── index.ts
│ │ ├── AddonDrawer
│ │ │ └── index.ts
│ │ ├── BreadCrumbs
│ │ │ └── index.ts
│ │ ├── CouponDrawer
│ │ │ └── index.ts
│ │ ├── CouponModal
│ │ │ └── index.ts
│ │ ├── CouponTable
│ │ │ └── index.ts
│ │ ├── FeatureTable
│ │ │ └── index.ts
│ │ ├── GroupDrawer
│ │ │ └── index.ts
│ │ ├── GroupsTable
│ │ │ └── index.ts
│ │ ├── ApiDocs
│ │ │ └── index.ts
│ │ ├── ForceRunDrawer
│ │ │ └── index.ts
│ │ ├── InfiniteScroll
│ │ │ └── index.ts
│ │ ├── LineItemCoupon
│ │ │ └── index.ts
│ │ ├── SaveCardModal
│ │ │ └── index.ts
│ │ ├── SecretKeyDrawer
│ │ │ └── index.ts
│ │ ├── TierBreakdown
│ │ │ └── index.ts
│ │ ├── Wallet
│ │ │ └── index.ts
│ │ ├── WalletDebitCard
│ │ │ └── index.ts
│ │ ├── WalletTopupCard
│ │ │ └── index.ts
│ │ ├── AppliedTaxesTable
│ │ │ └── index.ts
│ │ ├── ImportFileDrawer
│ │ │ └── index.ts
│ │ ├── UpdatePriceDialog
│ │ │ └── index.ts
│ │ ├── WalletAlertDialog
│ │ │ └── index.ts
│ │ ├── AddEntitlementDrawer
│ │ │ └── index.ts
│ │ ├── CustomerUsageTable
│ │ │ └── index.ts
│ │ ├── EnvironmentSelector
│ │ │ └── index.ts
│ │ ├── InvoicePaymentsTable
│ │ │ └── index.ts
│ │ ├── PriceOverrideDialog
│ │ │ └── index.ts
│ │ ├── RecordPaymentTopup
│ │ │ └── index.ts
│ │ ├── ServiceAccountDrawer
│ │ │ └── index.ts
│ │ ├── TaxAssociationDialog
│ │ │ └── index.ts
│ │ ├── TaxAssociationTable
│ │ │ └── index.ts
│ │ ├── TerminateWalletModal
│ │ │ └── index.ts
│ │ ├── CommitmentConfigDialog
│ │ │ └── index.ts
│ │ ├── MetadataModal
│ │ │ └── index.ts
│ │ ├── NomodConnectionDrawer
│ │ │ └── index.ts
│ │ ├── StripeConnectionDrawer
│ │ │ └── index.ts
│ │ ├── TerminateLineItemModal
│ │ │ └── index.ts
│ │ ├── ChargeValueCell
│ │ │ └── index.ts
│ │ ├── ChargebeeConnectionDrawer
│ │ │ └── index.ts
│ │ ├── HubSpotConnectionDrawer
│ │ │ └── index.ts
│ │ ├── PremiumFeature
│ │ │ └── index.ts
│ │ ├── RazorpayConnectionDrawer
│ │ │ └── index.ts
│ │ ├── RolloutChargesModal
│ │ │ └── index.ts
│ │ ├── SubscriptionTable
│ │ │ └── index.ts
│ │ ├── DocsDrawer
│ │ │ └── index.ts
│ │ ├── FeatureAlertDialog
│ │ │ └── index.ts
│ │ ├── InvoiceCreditLineItemTable
│ │ │ └── index.ts
│ │ ├── SubscriptionTaxAssociationTable
│ │ │ └── index.ts
│ │ ├── CustomPopover
│ │ │ └── index.ts
│ │ ├── EventFilter
│ │ │ └── index.ts
│ │ ├── SubscriptionDiscountTable
│ │ │ └── index.ts
│ │ ├── PricingCard
│ │ │ └── index.ts
│ │ ├── Sidebar
│ │ │ ├── index.ts
│ │ │ └── UserProfile.tsx
│ │ ├── Tabs
│ │ │ ├── index.ts
│ │ │ └── CustomTabs.tsx
│ │ ├── DetailsCard
│ │ │ └── index.ts
│ │ ├── DropdownMenu
│ │ │ └── index.ts
│ │ ├── AddonTable
│ │ │ └── index.ts
│ │ ├── TerminatePriceModal
│ │ │ └── index.ts
│ │ ├── SubscriptionEntitlementsSection
│ │ │ └── index.ts
│ │ ├── RectangleRadiogroup
│ │ │ └── index.ts
│ │ ├── CreditNoteTable
│ │ │ └── index.ts
│ │ ├── InvoiceLineItemTable
│ │ │ └── index.ts
│ │ ├── EntitlementOverrides
│ │ │ └── index.ts
│ │ ├── Table
│ │ │ ├── index.ts
│ │ │ ├── RedirectCell.tsx
│ │ │ └── TooltipCell.tsx
│ │ ├── Dashboard
│ │ │ └── index.ts
│ │ ├── CreditGrant
│ │ │ └── index.ts
│ │ ├── Customer
│ │ │ └── index.ts
│ │ ├── InvoiceTable
│ │ │ ├── index.ts
│ │ │ └── CustomerInvoiceTable.tsx
│ │ ├── QueryBuilder
│ │ │ └── index.ts
│ │ ├── CustomerSubscription
│ │ │ └── SubscriptionPauseWarning.tsx
│ │ └── MetricCard.tsx
│ ├── organisms
│ │ ├── EmptyPage
│ │ │ └── index.ts
│ │ ├── PlanPriceTable
│ │ │ └── index.ts
│ │ ├── EntityChargesPage
│ │ │ └── index.ts
│ │ ├── PlanForm
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── Subscription
│ │ │ ├── index.ts
│ │ │ └── UsageTable.tsx
│ └── ui
│ │ ├── skeleton.tsx
│ │ ├── collapsible.tsx
│ │ ├── textarea.tsx
│ │ ├── label.tsx
│ │ ├── separator.tsx
│ │ ├── progress.tsx
│ │ ├── input.tsx
│ │ ├── slider.tsx
│ │ ├── checkbox.tsx
│ │ ├── badge.tsx
│ │ ├── tooltip.tsx
│ │ ├── switch.tsx
│ │ ├── popover.tsx
│ │ └── scroll-area.tsx
├── types
│ ├── common
│ │ ├── Table.ts
│ │ ├── BaseCadence.ts
│ │ ├── Filters.ts
│ │ ├── Environment.ts
│ │ ├── Coupon.ts
│ │ └── index.ts
│ ├── dto
│ │ ├── webhook.ts
│ │ ├── Auth.ts
│ │ ├── UserApi.ts
│ │ ├── base.ts
│ │ ├── Testimonial.ts
│ │ ├── Environment.ts
│ │ ├── Integration.ts
│ │ ├── LineItemCommitmentConfig.ts
│ │ ├── User.ts
│ │ ├── Tenant.ts
│ │ ├── SecretApi.ts
│ │ ├── Group.ts
│ │ ├── Payment.ts
│ │ ├── Coupon.ts
│ │ ├── CostSheet.ts
│ │ ├── CreditGrantApplication.ts
│ │ ├── Meter.ts
│ │ ├── CreditNote.ts
│ │ └── Task.ts
│ ├── index.ts
│ ├── formatters
│ │ ├── index.ts
│ │ ├── BaseEntity.ts
│ │ └── Feature.ts
│ ├── font.d.ts
│ └── enums
│ │ ├── ChargebeeWebhookEvents.ts
│ │ ├── QuickBooksWebhookEvents.ts
│ │ ├── RazorpayWebhookEvents.ts
│ │ └── NomodWebhookEvents.ts
├── vite-env.d.ts
├── utils
│ ├── helpers
│ │ ├── index.ts
│ │ ├── wallet.ts
│ │ └── coupons.ts
│ ├── index.ts
│ └── common
│ │ ├── format_chips.ts
│ │ ├── format_cadence_chip.ts
│ │ ├── format_number.ts
│ │ ├── api_helper.ts
│ │ ├── index.ts
│ │ ├── format_coupon_name.ts
│ │ └── credit_grant_helpers.ts
├── models
│ ├── Pagination.ts
│ ├── Event.ts
│ ├── Environment.ts
│ ├── Integration.ts
│ ├── CostSheet.ts
│ ├── Addon.ts
│ ├── base.ts
│ ├── Group.ts
│ ├── Plan.ts
│ ├── Customer.ts
│ ├── CustomerEntitlement.ts
│ ├── User.ts
│ ├── Coupon.ts
│ ├── expand.ts
│ ├── SecretKey.ts
│ ├── ImportTask.ts
│ ├── Connection.ts
│ ├── Tenant.ts
│ ├── WalletTransaction.ts
│ ├── Entitlement.ts
│ ├── ScheduledTask.ts
│ ├── Feature.ts
│ ├── Meter.ts
│ ├── CustomerUsage.ts
│ ├── WalletBalance.ts
│ ├── CreditGrant.ts
│ ├── Payment.ts
│ └── Analytics.ts
├── assets
│ └── fonts
│ │ └── qanelas
│ │ ├── QanelasBlack.otf
│ │ ├── QanelasBold.otf
│ │ ├── QanelasHeavy.otf
│ │ ├── QanelasLight.otf
│ │ ├── QanelasThin.otf
│ │ ├── QanelasMedium.otf
│ │ ├── QanelasRegular.otf
│ │ ├── QanelasBoldItalic.otf
│ │ ├── QanelasExtraBold.otf
│ │ ├── QanelasSemiBold.otf
│ │ ├── QanelasThinItalic.otf
│ │ ├── QanelasUltraLight.otf
│ │ ├── QanelasBlackItalic.otf
│ │ ├── QanelasHeavyItalic.otf
│ │ ├── QanelasLightItalic.otf
│ │ ├── QanelasMediumItalic.otf
│ │ ├── QanelasExtraBoldItalic.otf
│ │ ├── QanelasRegularItalic.otf
│ │ ├── QanelasSemiBoldItalic.otf
│ │ └── QanelasUltraLightItalic.otf
├── store
│ ├── index.ts
│ └── useApiDocsStore.ts
├── lib
│ ├── utils.ts
│ └── sizing.ts
├── core
│ ├── services
│ │ ├── vercel
│ │ │ └── vercel.tsx
│ │ ├── sentry
│ │ │ └── SentryProvider.tsx
│ │ ├── intercom
│ │ │ └── index.css
│ │ ├── posthog
│ │ │ └── PosthogProvider.tsx
│ │ └── supbase
│ │ │ └── config.ts
│ ├── axios
│ │ └── types.ts
│ └── auth
│ │ ├── AuthProvider.tsx
│ │ └── AuthService.ts
├── api
│ ├── WebhookApi.ts
│ ├── RbacApi.ts
│ ├── TenantApi.ts
│ ├── IntegrationsApi.ts
│ ├── AuthApi.ts
│ ├── CreditGrantApi.ts
│ ├── EnvironmentApi.ts
│ ├── CouponApi.ts
│ ├── MeterApi.ts
│ ├── ConnectionApi.ts
│ ├── SecretKeysApi.ts
│ ├── index.ts
│ └── ExportRunApi.ts
├── tests
│ └── setup.ts
├── constants
│ ├── index.ts
│ └── loading_quotes.ts
├── main.tsx
├── context
│ └── DocsContext.tsx
└── App.tsx
├── docs
├── search-filter-implementation.md
└── voidInvoice.md
├── .prettierignore
├── public
├── meta.json
├── newlogobrowser.png
├── assets
│ ├── logo
│ │ ├── logo.png
│ │ └── chargebee.png
│ ├── png
│ │ ├── aftershoot.png
│ │ └── ic_login_bg.png
│ ├── company-founders
│ │ ├── clueso.png
│ │ ├── chaabi.webp
│ │ ├── krutrim.png
│ │ ├── publive.webp
│ │ ├── verniq.webp
│ │ ├── aftershoot.png
│ │ ├── simplismart.png
│ │ ├── truffleai.png
│ │ ├── wizcommerce.png
│ │ ├── simplismart.webp
│ │ ├── wizcommerce.webp
│ │ ├── 1732115195410.jpeg
│ │ └── 1747891553125.jpeg
│ ├── company-logo
│ │ ├── Clueso Logo.png
│ │ ├── aftershoot.webp
│ │ ├── krutrim logo.png
│ │ ├── Truffle AI Logo.png
│ │ ├── aftershoot copy.png
│ │ ├── aftershoot copy.tiff
│ │ ├── Y_Combinator_logo.svg.png
│ │ └── quickbooks.svg
│ └── csv
│ │ └── sample.csv
├── ic_rounded_flexprice.svg
└── favicon.svg
├── .husky
└── pre-commit
├── assets
├── open-arch.jpg
├── open-arch.png
├── struggle.png
├── flexprice_logo.png
└── complex-iterations.png
├── vite.config.d.ts
├── postcss.config.js
├── vercel.json
├── nginx.conf
├── .storybook
├── preview.ts
└── main.ts
├── docker-compose.yml
├── tsconfig.app.json
├── index.html
├── scripts
└── generate-meta.js
├── Dockerfile
├── .dockerignore
├── nginx
└── nginx.conf
├── components.json
├── SECURITY.md
├── .prettierrc.cjs
├── vite.config.ts
├── .gitignore
├── vitest.config.ts
├── vite.config.js
├── tsconfig.node.json
├── .env.example
├── eslint.config.js
├── tsconfig.json
├── setup.ps1
└── .github
└── workflows
├── demo.yaml
├── staging.yaml
└── production.yaml
/src/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/error-demo/ErrorDemo.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/search-filter-implementation.md:
--------------------------------------------------------------------------------
1 | 1.
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Ignore artifacts:
4 | build
--------------------------------------------------------------------------------
/public/meta.json:
--------------------------------------------------------------------------------
1 | {"versionId":"1a7422cf6447dc9fcdfebd51e59de791f875c086"}
--------------------------------------------------------------------------------
/src/components/atoms/Chip/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Chip';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Page/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Page';
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npm run format
2 | npx lint-staged
3 | npm run build
4 |
--------------------------------------------------------------------------------
/src/components/atoms/Dialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Dialog';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Divider/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Divider';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Input';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Label/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Label';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Modal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Modal';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Sheet/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Sheet';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Spacer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Spacer';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Spinner/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Spinner';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Stepper/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Stepper';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Toggle/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Toggle';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Tooltip';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Checkbox';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/CodeBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CodeBlock';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Progress/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Progress';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Textarea/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Textarea';
2 |
--------------------------------------------------------------------------------
/src/pages/error/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ErrorPage } from './ErrorPage';
2 |
--------------------------------------------------------------------------------
/src/pages/settings/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Billing } from './Billing';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/ActionButton/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ActionButton';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/Card/index.ts:
--------------------------------------------------------------------------------
1 | export { default, CardHeader } from './Card';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/CodePreview/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CodePreview';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/ComingSoon/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ComingSoon';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/DatePicker/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DatePicker';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/FormHeader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './FormHeader';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/MultiSelect/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './MultiSelect';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/NoDataCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './NoDataCard';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/DebugMenu/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DebugMenu';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/Events/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './EventsTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/Pagination/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Pagination';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/PlanDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PlanDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/PlansTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PlansTable';
2 |
--------------------------------------------------------------------------------
/src/components/organisms/EmptyPage/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './EmptyPage';
2 |
--------------------------------------------------------------------------------
/src/pages/home/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DashboardPage } from './DashboardPage';
2 |
--------------------------------------------------------------------------------
/src/types/common/Table.ts:
--------------------------------------------------------------------------------
1 | export type { ColumnData } from '@/components/molecules';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/DateTimePicker/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DateTimePicker';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/MultichipInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './MultiChipInput';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/SectionHeader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SectionHeader';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/SelectFeature/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SelectFeature';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/AddonDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AddonDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/BreadCrumbs/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './BreadCrumbs';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/CouponDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CouponDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/CouponModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CouponModal';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/CouponTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CouponTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/FeatureTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './FeatureTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/GroupDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './GroupDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/GroupsTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './GroupsTable';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/DateRangePicker/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DateRangePicker';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/DecimalUsageInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DecimalUsageInput';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/ShortPagination/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ShortPagination';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/ApiDocs/index.ts:
--------------------------------------------------------------------------------
1 | export { default, ApiDocsContent } from './ApiDocs';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/ForceRunDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ForceRunDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/InfiniteScroll/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './InfiniteScroll';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/LineItemCoupon/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './LineItemCoupon';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/SaveCardModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SaveCardModal';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/SecretKeyDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SecretKeyDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/TierBreakdown/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TierBreakdown';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/Wallet/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './WalletTransactionsTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/WalletDebitCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './WalletDebitCard';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/WalletTopupCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './WalletTopupCard';
2 |
--------------------------------------------------------------------------------
/src/components/organisms/PlanPriceTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PlanPriceTable';
2 |
--------------------------------------------------------------------------------
/src/pages/webhooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as WebhookDashboard } from './WebhookDashboard';
2 |
--------------------------------------------------------------------------------
/assets/open-arch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/assets/open-arch.jpg
--------------------------------------------------------------------------------
/assets/open-arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/assets/open-arch.png
--------------------------------------------------------------------------------
/assets/struggle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/assets/struggle.png
--------------------------------------------------------------------------------
/src/components/atoms/FeatureMultiSelect/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './FeatureMultiSelect';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/AppliedTaxesTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AppliedTaxesTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/ImportFileDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ImportFileDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/UpdatePriceDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './UpdatePriceDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/WalletAlertDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './WalletAlertDialog';
2 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | declare const __APP_VERSION__: string;
3 |
--------------------------------------------------------------------------------
/vite.config.d.ts:
--------------------------------------------------------------------------------
1 | declare const _default: import('vite').UserConfig;
2 | export default _default;
3 |
--------------------------------------------------------------------------------
/src/components/molecules/AddEntitlementDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AddEntitlementDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/CustomerUsageTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CustomerUsageTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/EnvironmentSelector/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './EnvironmentSelector';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/InvoicePaymentsTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './InvoicePaymentsTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/PriceOverrideDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PriceOverrideDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/RecordPaymentTopup/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './RecordPaymentTopup';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/ServiceAccountDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ServiceAccountDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/TaxAssociationDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TaxAssociationDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/TaxAssociationTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TaxAssociationTable';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/TerminateWalletModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TerminateWalletModal';
2 |
--------------------------------------------------------------------------------
/assets/flexprice_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/assets/flexprice_logo.png
--------------------------------------------------------------------------------
/public/newlogobrowser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/newlogobrowser.png
--------------------------------------------------------------------------------
/src/components/atoms/PaymentUrlSuccessDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PaymentUrlSuccessDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/CommitmentConfigDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CommitmentConfigDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/MetadataModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MetadataModal } from './MetadataModal';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/NomodConnectionDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './NomodConnectionDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/StripeConnectionDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './StripeConnectionDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/TerminateLineItemModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TerminateLineItemModal';
2 |
--------------------------------------------------------------------------------
/public/assets/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/logo/logo.png
--------------------------------------------------------------------------------
/src/components/molecules/ChargeValueCell/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ChargeValueCell } from './ChargeValueCell';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/ChargebeeConnectionDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ChargebeeConnectionDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/HubSpotConnectionDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './HubSpotConnectionDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/PremiumFeature/index.ts:
--------------------------------------------------------------------------------
1 | export { default, PremiumFeatureTag } from './PremiumFeature';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/RazorpayConnectionDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './RazorpayConnectionDrawer';
2 |
--------------------------------------------------------------------------------
/assets/complex-iterations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/assets/complex-iterations.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/atoms/Loader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Loader';
2 | export { PageLoader } from './Loader';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/RolloutChargesModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default, RolloutOption } from './RolloutChargesModal';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/SubscriptionTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SubscriptionTable } from './SubscriptionTable';
2 |
--------------------------------------------------------------------------------
/src/utils/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './coupons';
2 | export * from './subscription';
3 | export * from './wallet';
4 |
--------------------------------------------------------------------------------
/public/assets/logo/chargebee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/logo/chargebee.png
--------------------------------------------------------------------------------
/public/assets/png/aftershoot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/png/aftershoot.png
--------------------------------------------------------------------------------
/public/assets/png/ic_login_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/png/ic_login_bg.png
--------------------------------------------------------------------------------
/src/components/molecules/DocsDrawer/index.ts:
--------------------------------------------------------------------------------
1 | export { default, SnippetBlock, type SupportedLanguage } from './DocsDrawer';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/FeatureAlertDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default as FeatureAlertDialog } from './FeatureAlertDialog';
2 |
--------------------------------------------------------------------------------
/src/components/molecules/InvoiceCreditLineItemTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './InvoiceCreditLineItemTable.tsx';
2 |
--------------------------------------------------------------------------------
/src/models/Pagination.ts:
--------------------------------------------------------------------------------
1 | export interface Pagination {
2 | offset?: number;
3 | limit?: number;
4 | total?: number;
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/dto/webhook.ts:
--------------------------------------------------------------------------------
1 | export interface WebhookDashboardResponse {
2 | url: string;
3 | svix_enabled: boolean;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/molecules/SubscriptionTaxAssociationTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SubscriptionTaxAssociationTable';
2 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | // Common utilities
2 | export * from './common';
3 |
4 | // Helper utilities
5 | export * from './helpers';
6 |
--------------------------------------------------------------------------------
/src/components/molecules/CustomPopover/index.ts:
--------------------------------------------------------------------------------
1 | import CustomPopover from './CustomPopover';
2 |
3 | export default CustomPopover;
4 |
--------------------------------------------------------------------------------
/public/assets/company-founders/clueso.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/clueso.png
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasBlack.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasBlack.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasBold.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasHeavy.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasHeavy.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasLight.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasLight.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasThin.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasThin.otf
--------------------------------------------------------------------------------
/src/components/atoms/RadioGroup/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './RadioGroup';
2 | export type { RadioMenuItem } from './RadioGroup';
3 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | export { useApiDocsStore } from './useApiDocsStore';
2 | export { useBreadcrumbsStore } from './useBreadcrumbsStore';
3 |
--------------------------------------------------------------------------------
/src/types/common/BaseCadence.ts:
--------------------------------------------------------------------------------
1 | export enum CadenceStatus {
2 | ONCE = 'once',
3 | REPEAT = 'repeat',
4 | FOREVER = 'forever',
5 | }
6 |
--------------------------------------------------------------------------------
/public/assets/company-founders/chaabi.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/chaabi.webp
--------------------------------------------------------------------------------
/public/assets/company-founders/krutrim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/krutrim.png
--------------------------------------------------------------------------------
/public/assets/company-founders/publive.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/publive.webp
--------------------------------------------------------------------------------
/public/assets/company-founders/verniq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/verniq.webp
--------------------------------------------------------------------------------
/public/assets/company-logo/Clueso Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/Clueso Logo.png
--------------------------------------------------------------------------------
/public/assets/company-logo/aftershoot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/aftershoot.webp
--------------------------------------------------------------------------------
/public/assets/company-logo/krutrim logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/krutrim logo.png
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasMedium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasMedium.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasRegular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasRegular.otf
--------------------------------------------------------------------------------
/src/components/molecules/EventFilter/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './EventFilter';
2 | export type { EventFilterData } from './EventFilter';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/SubscriptionDiscountTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SubscriptionDiscountTable } from './SubscriptionDiscountTable';
2 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/"
6 | }
7 | ]
8 | }
--------------------------------------------------------------------------------
/public/assets/company-founders/aftershoot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/aftershoot.png
--------------------------------------------------------------------------------
/public/assets/company-founders/simplismart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/simplismart.png
--------------------------------------------------------------------------------
/public/assets/company-founders/truffleai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/truffleai.png
--------------------------------------------------------------------------------
/public/assets/company-founders/wizcommerce.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/wizcommerce.png
--------------------------------------------------------------------------------
/public/assets/company-logo/Truffle AI Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/Truffle AI Logo.png
--------------------------------------------------------------------------------
/public/assets/company-logo/aftershoot copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/aftershoot copy.png
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasBoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasBoldItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasExtraBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasExtraBold.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasSemiBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasSemiBold.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasThinItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasThinItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasUltraLight.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasUltraLight.otf
--------------------------------------------------------------------------------
/src/components/molecules/PricingCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PricingCard';
2 | export type { PricingCardProps } from './PricingCard';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/Sidebar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Sidebar } from './Sidebar';
2 | export { default as SidbarMenu } from './SidebarMenu';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/Tabs/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CustomTabs } from './CustomTabs';
2 | export { default as FlatTabs } from './FlatTabs';
3 |
--------------------------------------------------------------------------------
/public/assets/company-founders/simplismart.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/simplismart.webp
--------------------------------------------------------------------------------
/public/assets/company-founders/wizcommerce.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/wizcommerce.webp
--------------------------------------------------------------------------------
/public/assets/company-logo/aftershoot copy.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/aftershoot copy.tiff
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasBlackItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasBlackItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasHeavyItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasHeavyItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasLightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasLightItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasMediumItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasMediumItalic.otf
--------------------------------------------------------------------------------
/src/components/molecules/DetailsCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DetailsCard } from './DetailsCard';
2 | export type { Detail } from './DetailsCard';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/DropdownMenu/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DropdownMenu';
2 | export type { DropdownMenuOption } from './DropdownMenu';
3 |
--------------------------------------------------------------------------------
/src/components/organisms/EntityChargesPage/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './EntityChargesPage';
2 | export { ENTITY_TYPE } from './EntityChargesPage';
3 |
--------------------------------------------------------------------------------
/public/assets/company-founders/1732115195410.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/1732115195410.jpeg
--------------------------------------------------------------------------------
/public/assets/company-founders/1747891553125.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-founders/1747891553125.jpeg
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasExtraBoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasExtraBoldItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasRegularItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasRegularItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasSemiBoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasSemiBoldItalic.otf
--------------------------------------------------------------------------------
/src/components/atoms/Combobox/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Combobox } from './Combobox';
2 | export type { ComboboxOption, ComboboxProps } from './Combobox';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/AddonTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AddonTable } from './AddonTable';
2 | export { default as AddonModal } from './AddonModal';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/TerminatePriceModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TerminatePriceModal';
2 | export { SyncOption } from './TerminatePriceModal';
3 |
--------------------------------------------------------------------------------
/src/pages/developer/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DeveloperPage } from './developer';
2 | export { default as ServiceAccountsPage } from './ServiceAccounts';
3 |
--------------------------------------------------------------------------------
/public/assets/company-logo/Y_Combinator_logo.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/public/assets/company-logo/Y_Combinator_logo.svg.png
--------------------------------------------------------------------------------
/src/assets/fonts/qanelas/QanelasUltraLightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flexprice/flexprice-front/HEAD/src/assets/fonts/qanelas/QanelasUltraLightItalic.otf
--------------------------------------------------------------------------------
/src/components/molecules/SubscriptionEntitlementsSection/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SubscriptionEntitlementsSection } from './SubscriptionEntitlementsSection';
2 |
--------------------------------------------------------------------------------
/src/components/atoms/CheckboxRadioGroup/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CheckboxRadioGroup';
2 | export type { CheckboxRadioGroupItem } from './CheckboxRadioGroup';
3 |
--------------------------------------------------------------------------------
/src/types/common/Filters.ts:
--------------------------------------------------------------------------------
1 | export interface Filters {
2 | expand?: string;
3 | limit?: number;
4 | offset?: number;
5 | order?: string;
6 | sort?: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // Common types
2 | export * from './common';
3 |
4 | // DTO types
5 | export * from './dto';
6 |
7 | // Formatters
8 | export * from './formatters';
9 |
--------------------------------------------------------------------------------
/src/components/molecules/RectangleRadiogroup/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './RectangleRadiogroup';
2 | export type { RectangleRadiogroupOption } from './RectangleRadiogroup';
3 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 3000;
3 | location / {
4 | root /usr/share/nginx/html;
5 | index index.html;
6 | try_files $uri $uri/ /index.html;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/components/atoms/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Button } from './Button';
2 | export { default as AddButton } from './AddButton';
3 | export type { ButtonProps } from './Button';
4 |
--------------------------------------------------------------------------------
/src/components/atoms/ErrorBoundary/index.ts:
--------------------------------------------------------------------------------
1 | export { ErrorBoundary, ErrorFallback, RouterErrorElement } from './ErrorBoundary';
2 | export { ErrorBoundary as default } from './ErrorBoundary';
3 |
--------------------------------------------------------------------------------
/src/components/molecules/CreditNoteTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CreditNoteTable } from './CreditNoteTable';
2 | export { default as CreditNoteLineItemTable } from './CreditNoteLineItemTable';
3 |
--------------------------------------------------------------------------------
/public/assets/csv/sample.csv:
--------------------------------------------------------------------------------
1 | event_name,external_customer_id,event_id,timestamp,source
2 | authentication,cus_001,event_001,2025-02-14T10:30:00Z,Web
3 | sign_up,cus_002,event_001,2025-02-14T11:00:00Z,Mobile
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/molecules/InvoiceLineItemTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './InvoiceLineItemTable';
2 | export { default as SubscriptionPreviewLineItemTable } from './SubscriptionPreviewLineItemTable';
3 |
--------------------------------------------------------------------------------
/src/pages/onboarding/index.ts:
--------------------------------------------------------------------------------
1 | export { default as OnboardingPage } from './onboarding';
2 | export type { TutorialItem } from './onboarding';
3 | export { default as OnboardingTenant } from './OnboardingTenant';
4 |
--------------------------------------------------------------------------------
/src/components/molecules/EntitlementOverrides/index.ts:
--------------------------------------------------------------------------------
1 | export { default as EntitlementOverridesTable } from './EntitlementOverridesTable';
2 | export { default as EditEntitlementDrawer } from './EditEntitlementDrawer';
3 |
--------------------------------------------------------------------------------
/src/core/services/vercel/vercel.tsx:
--------------------------------------------------------------------------------
1 | import { SpeedInsights } from '@vercel/speed-insights/react';
2 |
3 | const VercelSpeedInsights = () => {
4 | return ;
5 | };
6 |
7 | export default VercelSpeedInsights;
8 |
--------------------------------------------------------------------------------
/src/types/formatters/index.ts:
--------------------------------------------------------------------------------
1 | export { formatMeterUsageResetPeriodToDisplay } from './Feature';
2 | export { sanitizeFilterConditions, sanitizeSortConditions } from './QueryBuilder';
3 | export type { TypedBackendFilter } from './QueryBuilder';
4 |
--------------------------------------------------------------------------------
/src/types/common/Environment.ts:
--------------------------------------------------------------------------------
1 | export enum NodeEnv {
2 | LOCAL = 'local',
3 | DEV = 'development',
4 | PROD = 'production',
5 | SELF_HOSTED = 'self-hosted',
6 | }
7 |
8 | export const NODE_ENV: NodeEnv = import.meta.env.VITE_ENVIRONMENT as NodeEnv;
9 |
--------------------------------------------------------------------------------
/docs/voidInvoice.md:
--------------------------------------------------------------------------------
1 | ### Problem statement
2 | Whenever we void an invoice, we need to allow the option to add metadata, key value pairs.
3 |
4 |
5 | ### GOALS:
6 | 1. Analyze the current setup
7 | 2. Make sure you reuse the compoenet already created for metadata
8 |
--------------------------------------------------------------------------------
/src/components/organisms/PlanForm/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PlanDetailsSection } from './PlanDetailsSection';
2 | export { default as SetupChargesSection } from './SetupChargesSection';
3 | export { default as RecurringChargesForm } from './RecurringChargesForm';
4 |
--------------------------------------------------------------------------------
/src/pages/usage/index.ts:
--------------------------------------------------------------------------------
1 | // Events
2 | export { default as Events } from './events/Events';
3 |
4 | // Query
5 | export { default as Query } from './query/Query';
6 |
7 | // Cost Analytics
8 | export { default as CostAnalytics } from './cost-analytics/CostAnalytics';
9 |
--------------------------------------------------------------------------------
/src/components/atoms/ComingSoon/ComingSoon.tsx:
--------------------------------------------------------------------------------
1 | const ComingSoonTag = () => {
2 | return (
3 |
Coming Soon
4 | );
5 | };
6 |
7 | export default ComingSoonTag;
8 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 |
3 | function Skeleton({ className, ...props }: React.HTMLAttributes) {
4 | return
;
5 | }
6 |
7 | export { Skeleton };
8 |
--------------------------------------------------------------------------------
/src/components/molecules/Table/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Table';
2 | export { default } from './Table';
3 | export { default as TooltipCell } from './TooltipCell';
4 | export { default as RedirectCell } from './RedirectCell';
5 | export { default as Toolbar, type FilterState } from './Toolbar';
6 |
--------------------------------------------------------------------------------
/src/models/Event.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export interface Event extends BaseModel {
4 | readonly customer_id: string;
5 | readonly event_name: string;
6 | readonly external_customer_id: string;
7 | readonly properties: Record;
8 | readonly source: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/types/common/Coupon.ts:
--------------------------------------------------------------------------------
1 | export enum COUPON_TYPE {
2 | FIXED = 'fixed',
3 | PERCENTAGE = 'percentage',
4 | }
5 |
6 | export enum COUPON_CADENCE {
7 | ONCE = 'once',
8 | REPEATED = 'repeated',
9 | FOREVER = 'forever',
10 | }
11 |
12 | export interface CouponRules {
13 | [key: string]: any;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/molecules/Dashboard/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DashboardControls } from './DashboardControls';
2 | export { default as RecentSubscriptionsCard } from './RecentSubscriptionsCard';
3 | export { default as RevenueTrendCard } from './RevenueTrendCard';
4 | export { default as InvoiceIssuesCard } from './InvoiceIssuesCard';
5 |
--------------------------------------------------------------------------------
/src/types/dto/Auth.ts:
--------------------------------------------------------------------------------
1 | export interface SignupData {
2 | email: string;
3 | password?: string;
4 | token?: string;
5 | }
6 |
7 | export interface LoginData {
8 | email: string;
9 | password: string;
10 | }
11 |
12 | export interface LocalUser {
13 | token: string;
14 | user_id: string;
15 | tenant_id: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/models/Environment.ts:
--------------------------------------------------------------------------------
1 | export interface Environment {
2 | id: string;
3 | name: string;
4 | type: ENVIRONMENT_TYPE;
5 | created_at: string;
6 | updated_at: string;
7 | }
8 |
9 | export enum ENVIRONMENT_TYPE {
10 | DEVELOPMENT = 'development',
11 | PRODUCTION = 'production',
12 | }
13 |
14 | export default Environment;
15 |
--------------------------------------------------------------------------------
/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css';
2 | import type { Preview } from '@storybook/react';
3 |
4 | const preview: Preview = {
5 | parameters: {
6 | controls: {
7 | matchers: {
8 | color: /(background|color)$/i,
9 | date: /Date$/i,
10 | },
11 | },
12 | },
13 | };
14 |
15 | export default preview;
16 |
--------------------------------------------------------------------------------
/src/types/dto/UserApi.ts:
--------------------------------------------------------------------------------
1 | import { User } from '@/models';
2 |
3 | export interface GetServiceAccountsResponse {
4 | items: User[];
5 | pagination?: {
6 | total: number;
7 | limit: number;
8 | offset: number;
9 | };
10 | }
11 |
12 | export interface CreateServiceAccountPayload {
13 | type: 'service_account';
14 | roles: string[];
15 | }
16 |
--------------------------------------------------------------------------------
/src/models/Integration.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export interface Integration extends BaseModel {
4 | readonly display_id: string;
5 | readonly expires_at: string;
6 | readonly last_used_at: string;
7 | readonly name: string;
8 | readonly permissions: string[];
9 | readonly provider: string;
10 | readonly type: string;
11 | }
12 |
--------------------------------------------------------------------------------
/src/types/dto/base.ts:
--------------------------------------------------------------------------------
1 | import { ENTITY_STATUS } from '@/models';
2 |
3 | export interface QueryFilter {
4 | limit?: number;
5 | offset?: number;
6 | status?: ENTITY_STATUS;
7 | sort?: string | any;
8 | order?: string;
9 | expand?: string;
10 | }
11 |
12 | export interface TimeRangeFilter {
13 | start_time?: string;
14 | end_time?: string;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/organisms/index.ts:
--------------------------------------------------------------------------------
1 | export { default as EmptyPage } from './EmptyPage';
2 | export { default as EntityChargesPage, ENTITY_TYPE } from './EntityChargesPage';
3 | export { default as PlanPriceTable } from './PlanPriceTable';
4 | export { AddonTable, PriceTable, SubscriptionActionButton, SubscriptionForm, SubscriptionTable, UsageTable } from './Subscription';
5 |
--------------------------------------------------------------------------------
/src/models/CostSheet.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 | import { Price } from './Price';
3 |
4 | export interface CostSheet extends BaseModel {
5 | readonly name: string;
6 | readonly description: string;
7 | readonly lookup_key: string;
8 | readonly metadata: Metadata;
9 | readonly prices?: Price[];
10 | }
11 |
12 | export default CostSheet;
13 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | container_name: flexprice-front
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | ports:
8 | - "3000:3000"
9 | healthcheck:
10 | test: ["CMD", "curl", "-f", "http://localhost:3000"]
11 | interval: 30s
12 | timeout: 10s
13 | retries: 3
14 | start_period: 40s
15 |
--------------------------------------------------------------------------------
/src/api/WebhookApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { WebhookDashboardResponse } from '@/types/dto/webhook';
3 |
4 | class WebhookApi {
5 | static async getWebhookDashboardUrl() {
6 | const baseUrl = '/webhooks';
7 | return AxiosClient.get(`${baseUrl}/dashboard`);
8 | }
9 | }
10 |
11 | export default WebhookApi;
12 |
--------------------------------------------------------------------------------
/src/components/molecules/CreditGrant/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SubscriptionCreditGrantTable } from './SubscriptionCreditGrantTable';
2 | export { default as CreditGrantsTable } from './CreditGrantsTable';
3 | export { default as CreditGrantModal } from './CreditGrantModal';
4 | export { default as UpcomingCreditGrantApplicationsTable } from './UpcomingCreditGrantApplicationsTable';
5 |
--------------------------------------------------------------------------------
/src/types/font.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.otf' {
2 | const src: string;
3 | export default src;
4 | }
5 |
6 | declare module '*.ttf' {
7 | const src: string;
8 | export default src;
9 | }
10 |
11 | declare module '*.woff' {
12 | const src: string;
13 | export default src;
14 | }
15 |
16 | declare module '*.woff2' {
17 | const src: string;
18 | export default src;
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "incremental": true,
5 | "skipLibCheck": true,
6 | "module": "ESNext",
7 | "moduleResolution": "bundler",
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "baseUrl": ".",
11 | "paths": {
12 | "@/*": ["./src/*"]
13 | }
14 | },
15 | "include": ["vite.config.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/molecules/Customer/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CreateCustomerDrawer } from './CreateCustomerDrawer';
2 | export { default as CustomerCard } from './CustomerCard';
3 | export { default as CustomerTable } from './CustomerTable';
4 | export { default as CustomerSearchSelect } from './CustomerSearchSelect';
5 | export type { CustomerSearchSelectProps } from './CustomerSearchSelect';
6 |
--------------------------------------------------------------------------------
/src/components/molecules/InvoiceTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './InvoiceTable';
2 | export { default as CustomerInvoiceTable } from './CustomerInvoiceTable';
3 | export { default as InvoiceTableMenu } from './InvoiceTableMenu';
4 | export { default as InvoiceStatusModal } from './InvoiceStatusModal';
5 | export { default as InvoicePaymentStatusModal } from './InvoicePaymentStatusModal';
6 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
2 |
3 | const Collapsible = CollapsiblePrimitive.Root;
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent };
10 |
--------------------------------------------------------------------------------
/src/types/dto/Testimonial.ts:
--------------------------------------------------------------------------------
1 | export interface Testimonial {
2 | companyTitleLogoUrl?: string;
3 | dpUrl: string;
4 | logoUrl: string;
5 | testimonial: string;
6 | name: string;
7 | designation: string;
8 | companyName: string;
9 | label?: string; // e.g., "Series A", "Series B", "YC 23", "YC 25"
10 | labelImageUrl?: string; // Image URL for label (e.g., Y Combinator logo)
11 | }
12 |
--------------------------------------------------------------------------------
/src/tests/setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 | import { expect, afterEach } from 'vitest';
3 | import { cleanup } from '@testing-library/react';
4 | import matchers from '@testing-library/jest-dom/matchers';
5 |
6 | // Extend Vitest's expect with Testing Library's matchers
7 | expect.extend(matchers);
8 |
9 | // Cleanup after each test
10 | afterEach(() => {
11 | cleanup();
12 | });
13 |
--------------------------------------------------------------------------------
/src/components/atoms/Select/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Select } from './Select';
2 | export { default as SearchableSelect } from './SearchableSelect';
3 | export { default as AsyncSearchableSelect } from './AsyncSearchableSelect';
4 | export type { SelectOption } from './Select';
5 | export type { AsyncSearchableSelectProps, SearchConfig, ExtractorsConfig, DisplayConfig, OptionsConfig } from './AsyncSearchableSelect';
6 |
--------------------------------------------------------------------------------
/src/models/Addon.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 |
3 | export enum ADDON_TYPE {
4 | ONETIME = 'onetime',
5 | MULTIPLE = 'multiple',
6 | }
7 |
8 | interface Addon extends BaseModel {
9 | readonly name: string;
10 | readonly description: string;
11 | readonly lookup_key: string;
12 | readonly type: ADDON_TYPE;
13 | readonly metadata: Metadata;
14 | }
15 |
16 | export default Addon;
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | flexprice.io
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/types/formatters/BaseEntity.ts:
--------------------------------------------------------------------------------
1 | import { ENTITY_STATUS } from '@/models';
2 |
3 | export const formatBaseEntityStatusToDisplay = (status: ENTITY_STATUS) => {
4 | switch (status) {
5 | case ENTITY_STATUS.PUBLISHED:
6 | return 'Published';
7 | case ENTITY_STATUS.DELETED:
8 | return 'Deleted';
9 | case ENTITY_STATUS.ARCHIVED:
10 | return 'Archived';
11 | default:
12 | return status;
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/types/dto/Environment.ts:
--------------------------------------------------------------------------------
1 | import { ENVIRONMENT_TYPE, Pagination, Environment } from '@/models';
2 |
3 | export interface UpdateEnvironmentPayload {
4 | name?: string;
5 | type?: ENVIRONMENT_TYPE;
6 | }
7 |
8 | export interface CreateEnvironmentPayload {
9 | name: string;
10 | type: ENVIRONMENT_TYPE;
11 | }
12 | export interface ListEnvironmentResponse extends Pagination {
13 | environments: Environment[];
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/generate-meta.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { execSync } from 'child_process';
4 |
5 | const versionId = execSync('git rev-parse HEAD').toString().trim();
6 |
7 | const meta = { versionId };
8 |
9 | const outPath = path.resolve(process.cwd(), 'public', 'meta.json');
10 | fs.writeFileSync(outPath, JSON.stringify(meta));
11 | console.log('Generated meta.json with versionId:', versionId);
12 |
--------------------------------------------------------------------------------
/src/utils/common/format_chips.ts:
--------------------------------------------------------------------------------
1 | import { ENTITY_STATUS } from '@/models';
2 |
3 | const formatChips = (data: string): string => {
4 | switch (data) {
5 | case ENTITY_STATUS.PUBLISHED:
6 | return 'Active';
7 | case ENTITY_STATUS.ARCHIVED:
8 | return 'Inactive';
9 | case ENTITY_STATUS.DELETED:
10 | return 'Inactive';
11 | default:
12 | return 'Inactive';
13 | }
14 | };
15 |
16 | export default formatChips;
17 |
--------------------------------------------------------------------------------
/src/core/axios/types.ts:
--------------------------------------------------------------------------------
1 | export interface ServerError {
2 | success: false;
3 | error: { message: string; internal_error: string; details: Record };
4 | }
5 |
6 | // adds the same shape to the global namespace for legacy code, tests, etc.
7 | declare global {
8 | interface ServerError {
9 | success: false;
10 | error: { message: string; internal_error: string; details: Record };
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/dto/Integration.ts:
--------------------------------------------------------------------------------
1 | import { Integration, Pagination } from '@/models';
2 |
3 | export interface CreateIntegrationRequest {
4 | provider: string;
5 | credentials: {
6 | key: string;
7 | };
8 | name: string;
9 | }
10 |
11 | export interface LinkedinIntegrationResponse {
12 | providers: string[];
13 | }
14 |
15 | export interface IntegrationResponse {
16 | items: Integration[];
17 | pagination: Pagination;
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/common/format_cadence_chip.ts:
--------------------------------------------------------------------------------
1 | import { CadenceStatus } from '@/types/common';
2 |
3 | const formatCadenceChip = (data: string): string => {
4 | switch (data) {
5 | case CadenceStatus.ONCE:
6 | return 'Once';
7 | case CadenceStatus.REPEAT:
8 | return 'Repeat';
9 | case CadenceStatus.FOREVER:
10 | return 'Forever';
11 | default:
12 | return 'Once';
13 | }
14 | };
15 |
16 | export default formatCadenceChip;
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Stage 1: Build the React app
2 | FROM node:20-alpine AS build
3 |
4 | # Set working directory
5 | WORKDIR /app
6 |
7 | # Copy package files for dependency installation
8 | COPY package.json package-lock.json ./
9 |
10 | # Install dependencies with locked versions
11 | RUN npm ci
12 |
13 | # Copy all files to the container
14 | COPY . .
15 |
16 | # Build the Vite app
17 | RUN npm run build
18 |
19 | CMD ["npm", "run", "start"]
20 |
--------------------------------------------------------------------------------
/src/pages/customer/creditnotes/CreditNoteDetailsPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import CreditNoteDetails from './CreditNoteDetails';
3 |
4 | const CreditNoteDetailsPage = () => {
5 | const { credit_note_id: creditNoteId } = useParams();
6 |
7 | return (
8 | <>
9 |
10 | >
11 | );
12 | };
13 |
14 | export default CreditNoteDetailsPage;
15 |
--------------------------------------------------------------------------------
/src/pages/product-catalog/addons/AddonCharges.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import EntityChargesPage, { ENTITY_TYPE } from '@/components/organisms/EntityChargesPage';
3 |
4 | const AddonChargesPage = () => {
5 | const { addonId } = useParams<{ addonId: string }>();
6 |
7 | return ;
8 | };
9 |
10 | export default AddonChargesPage;
11 |
--------------------------------------------------------------------------------
/src/components/atoms/Spacer/Spacer.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 |
3 | interface Props {
4 | width?: string | number;
5 | height?: string | number;
6 | className?: string;
7 | }
8 | const Spacer: FC = ({ height, width, className }) => {
9 | return (
10 |
16 | );
17 | };
18 |
19 | export default Spacer;
20 |
--------------------------------------------------------------------------------
/src/models/base.ts:
--------------------------------------------------------------------------------
1 | export interface BaseModel {
2 | id: string;
3 | created_at: string;
4 | updated_at: string;
5 | created_by: string;
6 | updated_by: string;
7 | tenant_id: string;
8 | status: ENTITY_STATUS;
9 | environment_id: string;
10 | }
11 |
12 | export enum ENTITY_STATUS {
13 | PUBLISHED = 'published',
14 | DELETED = 'deleted',
15 | ARCHIVED = 'archived',
16 | }
17 |
18 | export interface Metadata {
19 | [key: string]: string;
20 | }
21 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .vercel
26 | tsconfig.tsbuildinfo
27 | tsconfig.app.tsbuildinfo
28 | # Sentry Config File
29 |
--------------------------------------------------------------------------------
/src/pages/product-catalog/cost-sheets/CostSheetCharges.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import { EntityChargesPage, ENTITY_TYPE } from '@/components/organisms';
3 |
4 | const CostSheetChargesPage = () => {
5 | const { costSheetId } = useParams<{ costSheetId: string }>();
6 |
7 | return ;
8 | };
9 |
10 | export default CostSheetChargesPage;
11 |
--------------------------------------------------------------------------------
/src/types/dto/LineItemCommitmentConfig.ts:
--------------------------------------------------------------------------------
1 | export enum CommitmentType {
2 | AMOUNT = 'amount',
3 | QUANTITY = 'quantity',
4 | }
5 |
6 | export interface LineItemCommitmentConfig {
7 | commitment_type: CommitmentType;
8 | commitment_amount?: number;
9 | commitment_quantity?: number;
10 | overage_factor: number;
11 | enable_true_up: boolean;
12 | is_window_commitment: boolean;
13 | }
14 |
15 | export type LineItemCommitmentsMap = Record;
16 |
--------------------------------------------------------------------------------
/src/models/Group.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 |
3 | export enum GROUP_ENTITY_TYPE {
4 | PRICE = 'price',
5 | PLAN = 'plan',
6 | ADDON = 'addon',
7 | FEATURE = 'feature',
8 | METER = 'meter',
9 | CUSTOMER = 'customer',
10 | }
11 |
12 | export interface Group extends BaseModel {
13 | readonly name: string;
14 | readonly lookup_key: string;
15 | readonly entity_type: GROUP_ENTITY_TYPE;
16 | readonly entity_ids: string[];
17 | readonly metadata: Metadata | null;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/atoms/NoDataCard/NoDataCard.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Card, { CardHeader } from '../Card';
3 |
4 | interface NoDataCardProps {
5 | title: string;
6 | subtitle: string;
7 | cta?: React.ReactNode;
8 | }
9 |
10 | const NoDataCard: FC = ({ title, subtitle, cta }) => {
11 | return (
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default NoDataCard;
19 |
--------------------------------------------------------------------------------
/src/hooks/usePagePosition.tsx:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect, useState } from 'react';
2 |
3 | export default function useWindowPosition() {
4 | const [scrollPosition, setPosition] = useState(0);
5 | useLayoutEffect(() => {
6 | function updatePosition() {
7 | setPosition(window.pageYOffset);
8 | }
9 | window.addEventListener('scroll', updatePosition);
10 | updatePosition();
11 | return () => window.removeEventListener('scroll', updatePosition);
12 | }, []);
13 | return scrollPosition;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/dto/User.ts:
--------------------------------------------------------------------------------
1 | export interface CreateUserRequest {
2 | name: string;
3 | email: string;
4 | password: string;
5 | }
6 |
7 | export interface UpdateTenantPayload {
8 | billing_details: {
9 | address: {
10 | address_line1: string;
11 | address_line2: string;
12 | address_city: string;
13 | address_state: string;
14 | address_postal_code: string;
15 | address_country: string;
16 | };
17 | email?: string;
18 | help_email?: string;
19 | phone?: string;
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from "@storybook/react-vite";
2 |
3 | const config: StorybookConfig = {
4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
5 | addons: [
6 | "@storybook/addon-onboarding",
7 | "@storybook/addon-essentials",
8 | "@chromatic-com/storybook",
9 | "@storybook/addon-interactions",
10 | ],
11 | framework: {
12 | name: "@storybook/react-vite",
13 | options: {},
14 | },
15 | };
16 | export default config;
17 |
--------------------------------------------------------------------------------
/src/models/Plan.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 | import { Entitlement } from './Entitlement';
3 | import { Price } from './Price';
4 | import { CreditGrant } from './CreditGrant';
5 |
6 | export interface Plan extends BaseModel {
7 | readonly description: string;
8 | readonly lookup_key: string;
9 | readonly name: string;
10 | readonly entitlements: Entitlement[];
11 | readonly prices: Price[];
12 | readonly credit_grants: CreditGrant[];
13 | readonly metadata?: Metadata;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/molecules/Sidebar/UserProfile.tsx:
--------------------------------------------------------------------------------
1 | const UserProfile = () => {
2 | return (
3 |
4 |
9 |
Simplismart
10 |
11 | );
12 | };
13 | export default UserProfile;
14 |
--------------------------------------------------------------------------------
/src/models/Customer.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 |
3 | export interface Customer extends BaseModel {
4 | address_city: string;
5 | address_country: string;
6 | address_line1: string;
7 | address_line2: string;
8 | address_postal_code: string;
9 | address_state: string;
10 | email: string;
11 | external_id: string;
12 | metadata: Metadata;
13 | name: string;
14 | environment_id: string;
15 | parent_customer_id?: string;
16 | parent_customer?: Customer;
17 | }
18 |
19 | export default Customer;
20 |
--------------------------------------------------------------------------------
/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 |
5 | server_name _;
6 |
7 | root /usr/share/nginx/html;
8 | index index.html;
9 |
10 | location / {
11 | try_files $uri /index.html;
12 | }
13 |
14 | # Enable Gzip Compression
15 | gzip on;
16 | gzip_types text/css text/javascript application/javascript application/json application/xml text/plain text/xml;
17 | gzip_vary on;
18 |
19 | # Security Headers
20 | add_header Content-Security-Policy "frame-ancestors 'self';" always;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/molecules/QueryBuilder/index.ts:
--------------------------------------------------------------------------------
1 | export { default as QueryBuilder } from './QueryBuilder';
2 | export { default as FilterPopover } from './FilterPopover';
3 | export { default as SortDropdown } from './SortDropdown';
4 | export { default as FilterMultiSelect } from './FilterMultiSelect';
5 | export type { FilterCondition, FilterField, FilterFieldType, FilterOperator, DataType, SortDirection } from '@/types/common/QueryBuilder';
6 | export { sanitizeFilterConditions, sanitizeSortConditions } from '@/types/formatters/QueryBuilder';
7 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/index.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/src/types/common/index.ts:
--------------------------------------------------------------------------------
1 | export { CadenceStatus } from './BaseCadence';
2 | export { COUPON_TYPE, COUPON_CADENCE } from './Coupon';
3 | export type { CouponRules } from './Coupon';
4 | export { FilterFieldType, DEFAULT_OPERATORS_PER_DATA_TYPE, DataType, FilterOperator, SortDirection } from './QueryBuilder';
5 | export type { FilterField, FilterCondition } from './QueryBuilder';
6 |
7 | // Environment types
8 | export { NodeEnv, NODE_ENV } from './Environment';
9 |
10 | // Common interface types
11 | export type { Filters } from './Filters';
12 |
--------------------------------------------------------------------------------
/src/models/CustomerEntitlement.ts:
--------------------------------------------------------------------------------
1 | import Feature from './Feature';
2 |
3 | export interface CustomerEntitlement {
4 | entitlement: {
5 | is_enabled: boolean;
6 | is_soft_limit: boolean;
7 | static_values: string[];
8 | usage_limit: number;
9 | usage_reset_period: string;
10 | };
11 | feature: Feature;
12 | sources: {
13 | entitlement_id: string;
14 | is_enabled: boolean;
15 | plan_id: string;
16 | plan_name: string;
17 | subscription_id: string;
18 | quantity: number;
19 | static_value: string;
20 | usage_limit: number;
21 | }[];
22 | }
23 |
--------------------------------------------------------------------------------
/src/models/User.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: string;
3 | tenant: {
4 | id: string;
5 | name: string;
6 | billing_details: {
7 | address: {
8 | address_line1: string;
9 | address_line2: string;
10 | address_city: string;
11 | address_state: string;
12 | address_postal_code: string;
13 | address_country: string;
14 | };
15 | };
16 | status: string;
17 | created_at: string;
18 | updated_at: string;
19 | };
20 | email: string;
21 | name?: string;
22 | type?: 'user' | 'service_account';
23 | roles?: string[];
24 | }
25 |
--------------------------------------------------------------------------------
/src/models/Coupon.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 | import { COUPON_TYPE, COUPON_CADENCE, CouponRules } from '@/types/common/Coupon';
3 |
4 | export interface Coupon extends BaseModel {
5 | name: string;
6 | redeem_after?: string;
7 | redeem_before?: string;
8 | max_redemptions?: number;
9 | total_redemptions: number;
10 | rules?: CouponRules;
11 | amount_off?: string;
12 | percentage_off?: string;
13 | type: COUPON_TYPE;
14 | cadence: COUPON_CADENCE;
15 | duration_in_periods?: number;
16 | currency: string;
17 | metadata?: Metadata;
18 | }
19 |
--------------------------------------------------------------------------------
/src/types/enums/ChargebeeWebhookEvents.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum for Chargebee webhook events used in the application
3 | * This provides type safety and prevents typos when working with webhook events
4 | */
5 | export enum ChargebeeWebhookEvents {
6 | // Payment events
7 | PAYMENT_SUCCEEDED = 'payment_succeeded',
8 | }
9 |
10 | /**
11 | * Helper function to get default webhook events
12 | * @returns Array of default Chargebee webhook events
13 | */
14 | export const getDefaultChargebeeWebhookEvents = (): ChargebeeWebhookEvents[] => [ChargebeeWebhookEvents.PAYMENT_SUCCEEDED];
15 |
--------------------------------------------------------------------------------
/src/types/enums/QuickBooksWebhookEvents.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum for QuickBooks webhook events used in the application
3 | * This provides type safety and prevents typos when working with webhook events
4 | */
5 | export enum QuickBooksWebhookEvents {
6 | // Payment events
7 | PAYMENT_CREATE = 'Payment.Create',
8 | }
9 |
10 | /**
11 | * Helper function to get default webhook events
12 | * @returns Array of default QuickBooks webhook events
13 | */
14 | export const getDefaultQuickBooksWebhookEvents = (): QuickBooksWebhookEvents[] => [QuickBooksWebhookEvents.PAYMENT_CREATE];
15 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | We value security for the project very highly. We encourage all users to report any vulnerabilities they discover to us.
6 | If you find a security vulnerability in the Flexprice project, please report it responsibly by sending an email to ola@flexprice.io
7 |
8 | At this juncture, we don't have a bug bounty program. We are a small team trying to solve a big problem. We urge you to report any vulnerabilities responsibly
9 | so that we can continue building a secure application for the entire community.
10 |
--------------------------------------------------------------------------------
/src/pages/auth/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Auth } from './Auth';
2 | export { default as EmailVerification } from './EmailVerification';
3 | export { default as ForgotPasswordForm } from './ForgotPasswordForm';
4 | export { default as GoogleSignin } from './GoogleSignin';
5 | export { default as LandingSection } from './LandingSection';
6 | export { default as LoginForm } from './LoginForm';
7 | export { default as ResendVerification } from './ResendVerification';
8 | export { default as SignupConfirmation } from './SignupConfirmation';
9 | export { default as SignupForm } from './SignupForm';
10 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'always',
3 | bracketSpacing: true,
4 | bracketSameLine: true,
5 | embeddedLanguageFormatting: 'auto',
6 | htmlWhitespaceSensitivity: 'css',
7 | insertPragma: false,
8 | jsxBracketSameLine: false,
9 | jsxSingleQuote: true,
10 | printWidth: 140,
11 | proseWrap: 'preserve',
12 | quoteProps: 'as-needed',
13 | requirePragma: false,
14 | semi: true,
15 | singleQuote: true,
16 | tabWidth: 2,
17 | trailingComma: 'all',
18 | useTabs: true,
19 | vueIndentScriptAndStyle: false,
20 | jsxBracketSameLine: true,
21 | endOfLine: 'auto',
22 | };
23 |
--------------------------------------------------------------------------------
/src/models/expand.ts:
--------------------------------------------------------------------------------
1 | export enum EXPAND {
2 | PRICES = 'prices',
3 | PLAN = 'plan',
4 | METERS = 'meters',
5 | FEATURES = 'features',
6 | PLANS = 'plans',
7 | ENTITLEMENTS = 'entitlements',
8 | SCHEDULE = 'schedule',
9 | INVOICE = 'invoice',
10 | SUBSCRIPTION = 'subscription',
11 | CUSTOMER = 'customer',
12 | CREDIT_NOTE = 'credit_note',
13 | CREDIT_GRANT = 'credit_grant',
14 | TAX_APPLIED = 'tax_applied',
15 | TAX_RATE = 'tax_rate',
16 | TAX_ASSOCIATION = 'tax_association',
17 | ADDONS = 'addons',
18 | PARENT_CUSTOMER = 'parent_customer',
19 | CREATED_BY_USER = 'created_by_user',
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/dto/Tenant.ts:
--------------------------------------------------------------------------------
1 | import { Subscription, CustomerUsage, Pagination, TenantBillingDetails, Metadata } from '@/models';
2 |
3 | export interface GetBillingdetailsResponse {
4 | subscriptions: Subscription[];
5 | usage: {
6 | customer_id: string;
7 | features: CustomerUsage[];
8 | pagination: Pagination;
9 | period: {
10 | end_time: string;
11 | period: string;
12 | start_time: string;
13 | };
14 | };
15 | }
16 |
17 | export interface UpdateTenantRequest {
18 | readonly name?: string;
19 | readonly billing_details?: TenantBillingDetails;
20 | readonly metadata?: Metadata;
21 | }
22 |
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // CONSTANTS EXPORTS
3 | // =============================================================================
4 |
5 | // Payment Constants
6 | export * from './payment';
7 |
8 | // Common Utilities
9 | export * from './common';
10 |
11 | // Constants
12 | export * from './constants';
13 |
14 | // Re-export model enums for convenience
15 | export { CREDIT_NOTE_TYPE, CREDIT_NOTE_STATUS, CREDIT_NOTE_REASON } from '@/models';
16 | export { INVOICE_TYPE as InvoiceType, INVOICE_CADENCE, BILLING_CADENCE } from '@/models';
17 |
--------------------------------------------------------------------------------
/src/components/atoms/Button/AddButton.tsx:
--------------------------------------------------------------------------------
1 | import { Plus } from 'lucide-react';
2 | import Button, { ButtonProps } from './Button';
3 | import { cn } from '@/lib/utils';
4 |
5 | interface AddButtonProps extends Omit {
6 | /**
7 | * Custom label text. Defaults to "Add"
8 | */
9 | label?: string;
10 | }
11 |
12 | const AddButton = ({ label = 'Add', className, children, ...props }: AddButtonProps) => {
13 | return (
14 | } className={cn('gap-1', className)} {...props}>
15 | {children || label}
16 |
17 | );
18 | };
19 |
20 | export default AddButton;
21 |
--------------------------------------------------------------------------------
/src/types/dto/SecretApi.ts:
--------------------------------------------------------------------------------
1 | import { Pagination, SecretKey } from '@/models';
2 |
3 | export interface GetAllSecretKeysResponse {
4 | items: SecretKey[];
5 | pagination: Pagination;
6 | }
7 |
8 | export interface CreateSecretKeyPayload {
9 | name: string;
10 | expires_at?: string;
11 | type: string;
12 | service_account_id?: string; // For service account API keys
13 | roles?: string[]; // Optional: for user account API keys with specific roles
14 | user_id?: string; // Optional: for service account API keys
15 | }
16 |
17 | export interface CreateSecretKeyResponse {
18 | api_key: string;
19 | secret: SecretKey;
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import react from '@vitejs/plugin-react';
3 | import fs from 'fs';
4 | import { defineConfig } from 'vite';
5 |
6 | const meta = JSON.parse(fs.readFileSync('./public/meta.json', 'utf8'));
7 |
8 | export default defineConfig({
9 | plugins: [react()],
10 | define: {
11 | __APP_VERSION__: JSON.stringify(meta.versionId),
12 | },
13 | resolve: {
14 | alias: {
15 | '@': path.resolve(__dirname, './src'),
16 | },
17 | },
18 | server: {
19 | cors: {
20 | origin: 'http://localhost:3000',
21 | methods: ['GET', 'POST'],
22 | },
23 | host: 'localhost',
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | yarn.lock
26 | package-lock.json
27 | .env
28 | .vercel
29 | tsconfig.tsbuildinfo
30 | tsconfig.app.tsbuildinfo
31 | # Sentry Config File
32 | .env.sentry-build-plugin
33 | .dev
34 | # dotenv-store encryption key
35 | .env.store.key
36 |
37 | # build
38 | vite.config.js
--------------------------------------------------------------------------------
/src/models/SecretKey.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export interface SecretKey extends BaseModel {
4 | readonly display_id: string;
5 | readonly expires_at?: string;
6 | readonly last_used_at: string;
7 | readonly name: string;
8 | readonly permissions: string[];
9 | readonly provider: string;
10 | readonly type: SECRET_KEY_TYPE;
11 | readonly user_id?: string;
12 | readonly roles?: string[];
13 | readonly user_type?: 'user' | 'service_account';
14 | }
15 |
16 | export enum SECRET_KEY_TYPE {
17 | PRIVATE_KEY = 'private_key',
18 | PUBLISHABLE_KEY = 'publishable_key',
19 | INTEGRATION = 'integration',
20 | }
21 |
--------------------------------------------------------------------------------
/src/api/RbacApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 |
3 | export interface RbacRole {
4 | id: string;
5 | name: string;
6 | description: string;
7 | permissions: {
8 | [entity: string]: string[];
9 | };
10 | }
11 |
12 | export interface GetRolesResponse {
13 | roles: RbacRole[];
14 | }
15 |
16 | class RbacApi {
17 | private static baseUrl = '/rbac';
18 |
19 | // Fetch all available roles
20 | public static async getAllRoles(): Promise {
21 | const response = await AxiosClient.get(`${this.baseUrl}/roles`);
22 | return response.roles;
23 | }
24 | }
25 |
26 | export default RbacApi;
27 |
--------------------------------------------------------------------------------
/src/hooks/use-mobile.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(undefined);
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12 | };
13 | mql.addEventListener('change', onChange);
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15 | return () => mql.removeEventListener('change', onChange);
16 | }, []);
17 |
18 | return !!isMobile;
19 | }
20 |
--------------------------------------------------------------------------------
/src/types/enums/RazorpayWebhookEvents.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum for Razorpay webhook events used in the application
3 | * This provides type safety and prevents typos when working with webhook events
4 | */
5 | export enum RazorpayWebhookEvents {
6 | // Payment events
7 | PAYMENT_CAPTURED = 'payment.captured',
8 | PAYMENT_FAILED = 'payment.failed',
9 | }
10 |
11 | /**
12 | * Helper function to get default webhook events
13 | * @returns Array of default Razorpay webhook events
14 | */
15 | export const getDefaultRazorpayWebhookEvents = (): RazorpayWebhookEvents[] => [
16 | RazorpayWebhookEvents.PAYMENT_CAPTURED,
17 | RazorpayWebhookEvents.PAYMENT_FAILED,
18 | ];
19 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import react from '@vitejs/plugin-react';
3 | import path from 'path';
4 |
5 | export default defineConfig({
6 | test: {
7 | globals: true,
8 | environment: 'jsdom',
9 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
10 | setupFiles: ['./src/tests/setup.ts'],
11 | coverage: {
12 | provider: 'v8',
13 | reporter: ['text', 'json', 'html'],
14 | exclude: [
15 | 'node_modules/',
16 | 'src/tests/setup.ts',
17 | ],
18 | },
19 | },
20 | plugins: [react()],
21 | resolve: {
22 | alias: {
23 | '@': path.resolve(__dirname, './src'),
24 | },
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/src/pages/index.ts:
--------------------------------------------------------------------------------
1 | // Auth pages
2 | export * from './auth';
3 |
4 | // Customer pages
5 | export * from './customer';
6 |
7 | // Developer pages
8 | export * from './developer';
9 |
10 | // Error pages
11 | export * from './error';
12 |
13 | // Insights tools pages
14 | export * from './insights-tools';
15 |
16 | // Onboarding pages
17 | export * from './onboarding';
18 |
19 | // Product catalog pages
20 | export * from './product-catalog';
21 |
22 | // Settings pages
23 | export * from './settings';
24 |
25 | // Usage pages
26 | export * from './usage';
27 |
28 | // Webhooks pages
29 | export * from './webhooks';
30 |
31 | // Home pages
32 | export * from './home';
33 |
--------------------------------------------------------------------------------
/src/components/atoms/Label/Label.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import React from 'react';
3 |
4 | interface LabelProps extends React.LabelHTMLAttributes {
5 | label: string;
6 | disabled?: boolean;
7 | labelClassName?: string;
8 | children?: React.ReactNode;
9 | }
10 |
11 | const Label = ({ label, disabled, labelClassName, children, htmlFor, ...props }: LabelProps) => {
12 | return (
13 |
17 | {label ? label : children}
18 |
19 | );
20 | };
21 |
22 | export default Label;
23 |
--------------------------------------------------------------------------------
/src/components/organisms/Subscription/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PriceTable } from './PriceTable';
2 | export { default as SubscriptionForm } from './SubscriptionForm';
3 | export { default as SubscriptionTable } from './SubscriptionTable';
4 | export { default as SubscriptionActionButton } from './SubscriptionActionButton';
5 | export { default as UsageTable } from './UsageTable';
6 | export { default as AddonTable } from './PriceTable';
7 | export { default as SubscriptionWithOverrides } from './SubscriptionWithOverrides';
8 | export { default as SubscriptionCreationExample } from './SubscriptionCreationExample';
9 | export { default as PriceOverrideSummary } from './PriceOverrideSummary';
10 |
--------------------------------------------------------------------------------
/src/store/useApiDocsStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 |
3 | export interface ApiDocsSnippet {
4 | label: string;
5 | description: string;
6 | curl: string;
7 | Python?: string;
8 | JavaScript?: string;
9 | PHP?: string;
10 | Java?: string;
11 | Go?: string;
12 | 'C#'?: string;
13 | Ruby?: string;
14 | Swift?: string;
15 | }
16 |
17 | interface ApiDocsState {
18 | snippets: ApiDocsSnippet[];
19 | setDocs: (snippets: ApiDocsSnippet[]) => void;
20 | clearDocs: () => void;
21 | }
22 |
23 | export const useApiDocsStore = create((set) => ({
24 | snippets: [],
25 | setDocs: (snippets) => set({ snippets }),
26 | clearDocs: () => set({ snippets: [] }),
27 | }));
28 |
--------------------------------------------------------------------------------
/src/utils/common/format_number.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Format a number with thousands separators and optional decimal places
3 | * @param value The number to format
4 | * @param decimals Number of decimal places (default: 0)
5 | * @returns Formatted number string
6 | */
7 | const formatNumber = (value: number, decimals: number = 0): string => {
8 | if (!value) return '-';
9 |
10 | // Clamp decimals to valid range (0-20)
11 | const clampedDecimals = Math.max(0, Math.min(20, decimals));
12 |
13 | return new Intl.NumberFormat('en-US', {
14 | minimumFractionDigits: clampedDecimals,
15 | maximumFractionDigits: clampedDecimals,
16 | }).format(value);
17 | };
18 |
19 | export default formatNumber;
20 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import react from '@vitejs/plugin-react';
3 | import fs from 'fs';
4 | import { defineConfig } from 'vite';
5 | var meta = JSON.parse(fs.readFileSync('./public/meta.json', 'utf8'));
6 | export default defineConfig({
7 | plugins: [react()],
8 | define: {
9 | __APP_VERSION__: JSON.stringify(meta.versionId),
10 | },
11 | resolve: {
12 | alias: {
13 | '@': path.resolve(__dirname, './src'),
14 | },
15 | },
16 | server: {
17 | cors: {
18 | origin: 'http://localhost:3000',
19 | methods: ['GET', 'POST'],
20 | },
21 | host: 'localhost',
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/src/models/ImportTask.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 |
3 | export interface ImportTask extends BaseModel {
4 | readonly task_type: string;
5 | readonly entity_type: string;
6 | readonly file_url: string;
7 | readonly file_name: string;
8 | readonly file_type: string;
9 | readonly task_status: string;
10 | readonly processed_records: number;
11 | readonly successful_records: number;
12 | readonly failed_records: number;
13 | readonly tenant_id: string;
14 | readonly completed_at: string;
15 | readonly error_summary: string;
16 | readonly failed_at: string;
17 | readonly metadata: Metadata;
18 | readonly started_at: string;
19 | readonly total_records: number;
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/common/api_helper.ts:
--------------------------------------------------------------------------------
1 | import { EXPAND } from '@/models/expand';
2 |
3 | export const generateExpandQueryParams = (expand: EXPAND[]): string => {
4 | return expand.join(',');
5 | };
6 |
7 | export const generateQueryParams = (baseUrl: string, params: Record): string => {
8 | const queryParams = Object.keys(params)
9 | .filter((key) => key && params[key] !== undefined && params[key] !== null)
10 | .map((key) => {
11 | const value = Array.isArray(params[key]) ? params[key].join(',') : params[key];
12 | return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
13 | })
14 | .join('&');
15 |
16 | return queryParams ? `${baseUrl}?${queryParams}` : baseUrl;
17 | };
18 |
--------------------------------------------------------------------------------
/src/models/Connection.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export interface Connection extends BaseModel {
4 | readonly name: string;
5 | readonly provider_type: string;
6 | readonly environment_id: string;
7 | readonly tenant_id: string;
8 | readonly connection_status: CONNECTION_STATUS;
9 | }
10 |
11 | export enum CONNECTION_PROVIDER_TYPE {
12 | STRIPE = 'stripe',
13 | RAZORPAY = 'razorpay',
14 | CHARGEBEE = 'chargebee',
15 | S3 = 's3',
16 | HUBSPOT = 'hubspot',
17 | QUICKBOOKS = 'quickbooks',
18 | NOMOD = 'nomod',
19 | // Add more providers as needed
20 | }
21 |
22 | export enum CONNECTION_STATUS {
23 | PUBLISHED = 'published',
24 | DRAFT = 'draft',
25 | ARCHIVED = 'archived',
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/useUser.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@tanstack/react-query';
2 | import { UserApi } from '@/api/UserApi';
3 | import AuthService from '@/core/auth/AuthService';
4 |
5 | const useUser = () => {
6 | const tokenStr = AuthService.getAcessToken();
7 |
8 | const {
9 | data: user,
10 | isLoading: loading,
11 | error,
12 | refetch,
13 | } = useQuery({
14 | queryKey: ['user', tokenStr],
15 | queryFn: async () => {
16 | return await UserApi.me();
17 | },
18 | enabled: !!tokenStr,
19 | retry: 4,
20 | retryDelay: 1000,
21 | // gcTime: 1000 * 60 * 5,
22 | // staleTime: 1000 * 60 * 5,
23 | });
24 |
25 | return { user, loading, error, refetch };
26 | };
27 |
28 | export default useUser;
29 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | const Textarea = React.forwardRef>(({ className, ...props }, ref) => {
6 | return (
7 |
15 | );
16 | });
17 | Textarea.displayName = 'Textarea';
18 |
19 | export { Textarea };
20 |
--------------------------------------------------------------------------------
/src/pages/insights-tools/index.ts:
--------------------------------------------------------------------------------
1 | // Integrations
2 | export { default as IntegrationDetails } from './integrations/IntegrationDetails';
3 | export { default as Integrations } from './integrations/Integrations';
4 | export { integrations } from './integrations/integrationsData';
5 | export { default as QuickBooksOAuthCallback } from './integrations/QuickBooksOAuthCallback';
6 |
7 | // Exports
8 | export { default as Exports } from './exports/Exports';
9 | export { default as S3Exports } from './exports/S3Exports';
10 | export { default as ExportManagement } from './exports/ExportManagement';
11 | export { default as ExportDetails } from './exports/ExportDetails';
12 | export { default as TaskRunsPage } from './exports/TaskRunsPage';
13 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5 | "target": "ES2022",
6 | "lib": ["ES2023"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/atoms/Divider/Divider.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 |
3 | interface DividerProps {
4 | color?: string;
5 | width?: string;
6 | alignment?: 'left' | 'center' | 'right';
7 | className?: string;
8 | }
9 |
10 | const Divider: FC = ({ color = '#E4E4E7', width = '100%', alignment = 'center', className }) => {
11 | const alignmentClass = alignment === 'left' ? 'justify-start' : alignment === 'right' ? 'justify-end' : 'justify-center';
12 |
13 | return (
14 |
22 | );
23 | };
24 |
25 | export default Divider;
26 |
--------------------------------------------------------------------------------
/public/assets/company-logo/quickbooks.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom/client';
2 | import App from './App.tsx';
3 | import './index.css';
4 | import PosthogProvider from './core/services/posthog/PosthogProvider.tsx';
5 | import SentryProvider from './core/services/sentry/SentryProvider.tsx';
6 | import VercelSpeedInsights from './core/services/vercel/vercel.tsx';
7 | import { NODE_ENV, NodeEnv } from './types/index.ts';
8 |
9 | const isProd = NODE_ENV === NodeEnv.PROD;
10 |
11 | ReactDOM.createRoot(document.getElementById('root')!).render(
12 |
13 | {isProd ? (
14 |
15 |
16 |
17 |
18 |
19 |
20 | ) : (
21 |
22 | )}
23 |
,
24 | );
25 |
--------------------------------------------------------------------------------
/src/api/TenantApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { Tenant } from '@/models';
3 | import { GetBillingdetailsResponse, UpdateTenantRequest } from '@/types/dto';
4 |
5 | class TenantApi {
6 | private static baseUrl = '/tenants';
7 |
8 | public static async getTenantById(id: string) {
9 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
10 | }
11 |
12 | public static async updateTenant(data: UpdateTenantRequest) {
13 | return await AxiosClient.put(`${this.baseUrl}/update`, data);
14 | }
15 |
16 | public static async getTenantBillingDetails() {
17 | return await AxiosClient.get(`${this.baseUrl}/billing`);
18 | }
19 | }
20 |
21 | export default TenantApi;
22 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as LabelPrimitive from '@radix-ui/react-label';
3 | import { cva, type VariantProps } from 'class-variance-authority';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70');
8 |
9 | const Label = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef & VariantProps
12 | >(({ className, ...props }, ref) => );
13 | Label.displayName = LabelPrimitive.Root.displayName;
14 |
15 | export { Label };
16 |
--------------------------------------------------------------------------------
/src/core/services/sentry/SentryProvider.tsx:
--------------------------------------------------------------------------------
1 | // src/SentryProvider.tsx
2 | import React from 'react';
3 | import * as Sentry from '@sentry/react';
4 |
5 | interface Props {
6 | children: React.ReactNode;
7 | }
8 | const isProd = import.meta.env.VITE_APP_ENVIRONMENT === 'prod';
9 |
10 | if (isProd) {
11 | Sentry.init({
12 | dsn: import.meta.env.VITE_APP_PUBLIC_SENTRY_DSN,
13 | integrations: [Sentry.browserTracingIntegration()],
14 | tracesSampleRate: 1.0,
15 | replaysSessionSampleRate: 0,
16 | replaysOnErrorSampleRate: 0,
17 | });
18 | }
19 |
20 | const SentryProvider = ({ children }: Props) => {
21 | return Something went wrong}>{children} ;
22 | };
23 |
24 | export default SentryProvider;
25 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as SeparatorPrimitive from '@radix-ui/react-separator';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Separator = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
10 |
17 | ));
18 | Separator.displayName = SeparatorPrimitive.Root.displayName;
19 |
20 | export { Separator };
21 |
--------------------------------------------------------------------------------
/src/utils/common/index.ts:
--------------------------------------------------------------------------------
1 | export { default as formatNumber } from './format_number';
2 | export { getCurrencySymbol, getCurrencyName, formatDateShort, formatBillingPeriodForPrice, getPriceTypeLabel } from './helper_functions';
3 | export { formatDateTime, formatDateTimeWithSecondsAndTimezone } from './format_date';
4 | export { getPriceTableCharge, calculateDiscountedPrice } from './price_helpers';
5 | export { default as formatCouponName } from './format_coupon_name';
6 | export type { ExtendedPriceOverride } from './price_override_helpers';
7 | export {
8 | hasCommitment,
9 | getCommitmentConfig,
10 | validateCommitment,
11 | formatCommitmentSummary,
12 | supportsWindowCommitment,
13 | extractLineItemCommitments,
14 | mergeCommitmentsIntoOverrides,
15 | } from './commitment_helpers';
16 |
--------------------------------------------------------------------------------
/src/pages/customer/payments/PaymentPage.tsx:
--------------------------------------------------------------------------------
1 | import { ApiDocsContent, FlatTabs } from '@/components/molecules';
2 | import { Page } from '@/components/atoms';
3 | import PaymentList from './PaymentList';
4 | import WalletTransactionList from './WalletTransactionList';
5 |
6 | const PaymentPage = () => {
7 | return (
8 |
9 |
10 | ,
16 | },
17 | {
18 | value: 'wallet-transactions',
19 | label: 'Wallet Transactions',
20 | content: ,
21 | },
22 | ]}
23 | />
24 |
25 | );
26 | };
27 |
28 | export default PaymentPage;
29 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Copy this file to .env and fill in your actual values
2 |
3 | # Supabase Configuration
4 | VITE_SUPABASE_URL=https://your-project-id.supabase.co
5 | VITE_SUPABASE_ANON_KEY=your-anon-key-here
6 |
7 | # App Environment (local | development | production | self-hosted)
8 | VITE_ENVIRONMENT=development
9 |
10 | # App Environment for feature flags (dev | prod)
11 | VITE_APP_ENVIRONMENT=dev
12 |
13 | # API URL
14 | VITE_API_URL=http://localhost:8080/v1
15 |
16 | # PostHog Analytics
17 | VITE_APP_PUBLIC_POSTHOG_KEY=your-posthog-key-here
18 | VITE_APP_PUBLIC_POSTHOG_HOST=https://app.posthog.com
19 |
20 | # Sentry Error Tracking (Optional)
21 | VITE_APP_PUBLIC_SENTRY_DSN=your-sentry-dsn-here
22 |
23 | # Intercom Support Chat (Optional)
24 | VITE_APP_INTERCOM_APP_ID=your-intercom-app-id-here
25 |
--------------------------------------------------------------------------------
/src/components/atoms/Spinner/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface SpinnerProps {
4 | size?: number;
5 | className?: string;
6 | }
7 |
8 | const Spinner: React.FC = ({ size = 24, className = '' }) => {
9 | return (
10 |
16 |
17 |
22 |
23 | );
24 | };
25 |
26 | export default Spinner;
27 |
--------------------------------------------------------------------------------
/src/models/Tenant.ts:
--------------------------------------------------------------------------------
1 | import { ENTITY_STATUS, Metadata } from './base';
2 |
3 | export interface TenantAddress {
4 | readonly address_line1: string;
5 | readonly address_line2: string;
6 | readonly address_city: string;
7 | readonly address_state: string;
8 | readonly address_postal_code: string;
9 | readonly address_country: string;
10 | }
11 |
12 | export interface TenantBillingDetails {
13 | readonly address: TenantAddress;
14 | readonly email: string;
15 | readonly help_email: string;
16 | readonly phone: string;
17 | }
18 |
19 | export interface Tenant {
20 | readonly name: string;
21 | readonly billing_details: TenantBillingDetails;
22 | readonly status: ENTITY_STATUS;
23 | readonly metadata: Metadata;
24 | }
25 |
26 | export enum TenantMetadataKey {
27 | ONBOARDING_COMPLETED = 'onboarding_completed',
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ProgressPrimitive from '@radix-ui/react-progress';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Progress = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, value, ...props }, ref) => (
10 |
11 |
15 |
16 | ));
17 | Progress.displayName = ProgressPrimitive.Root.displayName;
18 |
19 | export { Progress };
20 |
--------------------------------------------------------------------------------
/src/types/formatters/Feature.ts:
--------------------------------------------------------------------------------
1 | import { METER_AGGREGATION_TYPE, METER_USAGE_RESET_PERIOD } from '@/models/Meter';
2 |
3 | export const formatMeterUsageResetPeriodToDisplay = (usageResetPeriod: string) => {
4 | switch (usageResetPeriod) {
5 | case METER_USAGE_RESET_PERIOD.BILLING_PERIOD:
6 | return 'Periodic';
7 | case METER_USAGE_RESET_PERIOD.NEVER:
8 | return 'Cumulative';
9 | default:
10 | return usageResetPeriod;
11 | }
12 | };
13 |
14 | export const formatAggregationTypeToDisplay = (aggregationType: string) => {
15 | switch (aggregationType) {
16 | case METER_AGGREGATION_TYPE.SUM:
17 | return 'Sum';
18 | case METER_AGGREGATION_TYPE.COUNT:
19 | return 'Count';
20 | case METER_AGGREGATION_TYPE.COUNT_UNIQUE:
21 | return 'Count Unique';
22 | default:
23 | return aggregationType;
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | const Input = React.forwardRef>(({ className, type, ...props }, ref) => {
6 | return (
7 |
16 | );
17 | });
18 | Input.displayName = 'Input';
19 |
20 | export { Input };
21 |
--------------------------------------------------------------------------------
/src/lib/sizing.ts:
--------------------------------------------------------------------------------
1 | export const sizes = {
2 | xs: {
3 | height: 'h-6',
4 | padding: 'px-2 py-1',
5 | text: 'text-xs',
6 | display: '',
7 | },
8 | sm: {
9 | height: 'h-8',
10 | padding: 'px-2 py-1.5',
11 | text: 'text-xs',
12 | display: '',
13 | },
14 | default: {
15 | height: 'h-10',
16 | padding: 'px-3 py-2',
17 | text: 'text-sm',
18 | display: '',
19 | },
20 | lg: {
21 | height: 'h-12',
22 | padding: 'px-4 py-2.5',
23 | text: 'text-base',
24 | display: '',
25 | },
26 | icon: {
27 | height: 'h-9 w-9',
28 | padding: 'p-2',
29 | text: 'text-sm',
30 | display: 'flex items-center justify-center',
31 | },
32 | } as const;
33 |
34 | export type SizeConfig = {
35 | height: string;
36 | padding: string;
37 | text: string;
38 | display: string;
39 | };
40 |
41 | export type SizeVariant = keyof typeof sizes;
42 |
--------------------------------------------------------------------------------
/src/pages/error/ErrorPage.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Spacer } from '@/components/atoms';
2 | import { RouteNames } from '@/core/routes/Routes';
3 | import { TriangleAlert } from 'lucide-react';
4 | import { Link } from 'react-router';
5 |
6 | const ErrorPage = () => {
7 | return (
8 |
9 |
10 |
11 |
404 Error Page
12 |
Oops! Looks like you took a wrong turn
13 |
14 |
15 |
16 | Back to Home
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default ErrorPage;
25 |
--------------------------------------------------------------------------------
/src/pages/product-catalog/plans/AddCharges.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import { useQuery } from '@tanstack/react-query';
3 | import EntityChargesPage, { ENTITY_TYPE } from '@/components/organisms/EntityChargesPage';
4 | import { PlanApi } from '@/api';
5 | import { Loader } from '@/components/atoms';
6 |
7 | const AddChargesPage = () => {
8 | const { planId } = useParams<{ planId: string }>();
9 |
10 | const { data: planData, isLoading } = useQuery({
11 | queryKey: ['fetchPlan', planId],
12 | queryFn: async () => {
13 | return await PlanApi.getPlanById(planId!);
14 | },
15 | enabled: !!planId,
16 | });
17 |
18 | if (isLoading) {
19 | return ;
20 | }
21 |
22 | return ;
23 | };
24 |
25 | export default AddChargesPage;
26 |
--------------------------------------------------------------------------------
/src/utils/helpers/wallet.ts:
--------------------------------------------------------------------------------
1 | // Convert credits to currency amount
2 | // amount in the currency = number of credits * conversion_rate
3 | // ex if conversion_rate is 1, then 1 USD = 1 credit
4 | // ex if conversion_rate is 2, then 1 USD = 0.5 credits
5 | // ex if conversion_rate is 0.5, then 1 USD = 2 credits
6 | export const getCurrencyAmountFromCredits = (conversion_rate: number, amount: number) => {
7 | return amount * conversion_rate;
8 | };
9 |
10 | // Convert currency amount to credits
11 | // number of credits = amount in the currency / conversion_rate
12 | // ex if conversion_rate is 1, then 1 USD = 1 credit
13 | // ex if conversion_rate is 2, then 1 USD = 0.5 credits
14 | // ex if conversion_rate is 0.5, then 1 USD = 2 credits
15 | export const getCreditsFromCurrencyAmount = (conversion_rate: number, amount: number) => {
16 | return amount / conversion_rate;
17 | };
18 |
--------------------------------------------------------------------------------
/public/ic_rounded_flexprice.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/types/dto/Group.ts:
--------------------------------------------------------------------------------
1 | import { Group, GROUP_ENTITY_TYPE, Pagination, Metadata } from '@/models';
2 | import { QueryFilter } from './base';
3 |
4 | export interface CreateGroupRequest {
5 | name: string;
6 | lookup_key: string;
7 | entity_type: GROUP_ENTITY_TYPE;
8 | metadata?: Metadata;
9 | }
10 |
11 | export interface UpdateGroupRequest {
12 | name?: string;
13 | entity_ids?: string[];
14 | metadata?: Metadata;
15 | }
16 |
17 | export interface GroupResponse extends Omit {
18 | entity_ids: string[];
19 | }
20 |
21 | export interface ListGroupsResponse {
22 | items: GroupResponse[];
23 | pagination: Pagination;
24 | }
25 |
26 | export interface GroupFilter extends QueryFilter {
27 | entity_type?: GROUP_ENTITY_TYPE;
28 | name?: string;
29 | lookup_key?: string;
30 | }
31 |
32 | export interface AddEntityToGroupRequest {
33 | entity_ids: string[];
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/molecules/Table/RedirectCell.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react';
2 | import { Link } from 'react-router';
3 | import { ExternalLink } from 'lucide-react';
4 |
5 | interface Props {
6 | redirectUrl: string;
7 | children: ReactNode;
8 | allowRedirect?: boolean;
9 | }
10 |
11 | const RedirectCell: FC = ({ redirectUrl, children, allowRedirect = true }) => {
12 | if (!allowRedirect) {
13 | return {children}
;
14 | }
15 |
16 | return (
17 |
18 |
22 | {children}
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default RedirectCell;
30 |
--------------------------------------------------------------------------------
/src/utils/common/format_coupon_name.ts:
--------------------------------------------------------------------------------
1 | import { COUPON_CADENCE, COUPON_TYPE } from '@/types/common';
2 | import { getCurrencySymbol } from './helper_functions';
3 | import { Coupon } from '@/models/Coupon';
4 |
5 | const formatCouponName = (coupon: Coupon) => {
6 | let couponName = '';
7 | if (coupon.type === COUPON_TYPE.FIXED) {
8 | couponName = `${getCurrencySymbol(coupon.currency)} ${coupon.amount_off} off`;
9 | } else {
10 | couponName = `${coupon.percentage_off}% off`;
11 | }
12 | if (coupon.cadence === COUPON_CADENCE.ONCE) {
13 | couponName = `${couponName} once`;
14 | } else if (coupon.cadence === COUPON_CADENCE.REPEATED) {
15 | couponName = `${couponName} for ${coupon.duration_in_periods} billing cycles`;
16 | } else if (coupon.cadence === COUPON_CADENCE.FOREVER) {
17 | couponName = `${couponName} forever`;
18 | }
19 |
20 | return couponName;
21 | };
22 |
23 | export default formatCouponName;
24 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import globals from 'globals';
3 | import reactHooks from 'eslint-plugin-react-hooks';
4 | import reactRefresh from 'eslint-plugin-react-refresh';
5 | import tseslint from 'typescript-eslint';
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | '@typescript-eslint/no-explicit-any': 'warn',
23 | '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
24 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
25 | },
26 | },
27 | );
28 |
--------------------------------------------------------------------------------
/src/models/WalletTransaction.ts:
--------------------------------------------------------------------------------
1 | import { Metadata } from './base';
2 | import { Customer } from './Customer';
3 | import { User } from './User';
4 |
5 | export type WalletTransaction = {
6 | readonly amount: number;
7 | readonly balance_after: number;
8 | readonly balance_before: number;
9 | readonly created_at: string;
10 | readonly description: string;
11 | readonly id: string;
12 | readonly metadata: Metadata;
13 | readonly reference_id: string;
14 | readonly reference_type: string;
15 | readonly transaction_status: string;
16 | readonly type: string;
17 | readonly wallet_id: string;
18 | readonly credit_amount: number;
19 | readonly transaction_reason: string;
20 | readonly expiry_date: string;
21 | readonly priority?: number;
22 | readonly customer_id?: string;
23 | readonly created_by?: string;
24 | readonly customer?: Customer;
25 | readonly currency?: string;
26 | readonly created_by_user?: User;
27 | };
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": false,
10 | "declaration": true,
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "paths": {
23 | "@/*": ["./src/*"]
24 | },
25 | "noFallthroughCasesInSwitch": true
26 | },
27 | "include": ["src"],
28 | "references": [
29 | { "path": "./tsconfig.app.json" }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/api/IntegrationsApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { CreateIntegrationRequest, IntegrationResponse, LinkedinIntegrationResponse } from '@/types/dto';
3 | class IntegrationsApi {
4 | private static baseUrl = '/secrets/integrations';
5 |
6 | public static async installIntegration(request: CreateIntegrationRequest) {
7 | return await AxiosClient.post(`${this.baseUrl}/${request.provider}`, request);
8 | }
9 |
10 | public static async getIntegration(provider: string) {
11 | return await AxiosClient.get(`${this.baseUrl}/${provider}`);
12 | }
13 |
14 | public static async getLinkedInIntegration() {
15 | return await AxiosClient.get(`${this.baseUrl}/linked`);
16 | }
17 |
18 | public static async uninstallIntegration(provider: string) {
19 | return await AxiosClient.delete(`${this.baseUrl}/${provider}`);
20 | }
21 | }
22 |
23 | export default IntegrationsApi;
24 |
--------------------------------------------------------------------------------
/src/models/Entitlement.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 | import Feature from './Feature';
3 |
4 | export enum ENTITLEMENT_USAGE_RESET_PERIOD {
5 | MONTHLY = 'MONTHLY',
6 | ANNUAL = 'ANNUAL',
7 | WEEKLY = 'WEEKLY',
8 | DAILY = 'DAILY',
9 | QUARTERLY = 'QUARTERLY',
10 | HALF_YEARLY = 'HALF_YEARLY',
11 | NEVER = 'NEVER',
12 | }
13 |
14 | export enum ENTITLEMENT_ENTITY_TYPE {
15 | PLAN = 'PLAN',
16 | SUBSCRIPTION = 'SUBSCRIPTION',
17 | ADDON = 'ADDON',
18 | }
19 |
20 | export interface Entitlement extends BaseModel {
21 | readonly feature: Feature;
22 | readonly feature_id: string;
23 | readonly feature_type: string;
24 | readonly is_enabled: boolean;
25 | readonly is_soft_limit: boolean;
26 | readonly entity_type: ENTITLEMENT_ENTITY_TYPE;
27 | readonly entity_id: string;
28 | readonly static_value: string;
29 | readonly tenant_id: string;
30 | readonly usage_limit: number | null;
31 | readonly usage_reset_period: ENTITLEMENT_USAGE_RESET_PERIOD | null;
32 | }
33 |
--------------------------------------------------------------------------------
/src/context/DocsContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, FC, ReactNode } from 'react';
2 | import { useApiDocsStore, ApiDocsSnippet } from '@/store/useApiDocsStore';
3 |
4 | interface DocsContextProps {
5 | setPageDocs: (snippets: ApiDocsSnippet[]) => void;
6 | clearPageDocs: () => void;
7 | }
8 |
9 | const DocsContext = createContext(undefined);
10 |
11 | interface DocsProviderProps {
12 | children: ReactNode;
13 | }
14 |
15 | export const DocsProvider: FC = ({ children }) => {
16 | const { setDocs, clearDocs } = useApiDocsStore();
17 |
18 | const value = {
19 | setPageDocs: setDocs,
20 | clearPageDocs: clearDocs,
21 | };
22 |
23 | return {children} ;
24 | };
25 |
26 | export const useDocs = () => {
27 | const context = useContext(DocsContext);
28 | if (!context) {
29 | throw new Error('useDocs must be used within a DocsProvider');
30 | }
31 | return context;
32 | };
33 |
--------------------------------------------------------------------------------
/src/core/services/intercom/index.css:
--------------------------------------------------------------------------------
1 | /* Hide the default Intercom launcher completely */
2 | #intercom-launcher,
3 | .intercom-launcher,
4 | [data-testid='intercom-launcher'],
5 | .intercom-launcher-frame,
6 | .intercom-launcher-frame iframe,
7 | .intercom-launcher-frame .intercom-launcher-frame {
8 | display: none !important;
9 | visibility: hidden !important;
10 | opacity: 0 !important;
11 | pointer-events: none !important;
12 | position: absolute !important;
13 | left: -9999px !important;
14 | top: -9999px !important;
15 | }
16 |
17 | /* Ensure Intercom messenger can still be shown programmatically */
18 | .intercom-messenger,
19 | #intercom-container,
20 | .intercom-messenger-frame {
21 | display: block !important;
22 | visibility: visible !important;
23 | opacity: 1 !important;
24 | pointer-events: auto !important;
25 | }
26 |
27 | /* Custom styling for your IntercomMessenger component */
28 | .intercom-messenger-button {
29 | /* Add any custom styling for your help button here */
30 | }
31 |
--------------------------------------------------------------------------------
/src/types/enums/NomodWebhookEvents.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum for Nomod webhook events used in the application
3 | * This provides type safety and prevents typos when working with webhook events
4 | */
5 | export enum NomodWebhookEvents {
6 | // Invoice events
7 | INVOICE_CREATED = 'invoice.created',
8 | INVOICE_PAID = 'invoice.paid',
9 | INVOICE_FAILED = 'invoice.failed',
10 | // Payment link events
11 | PAYMENT_LINK_CREATED = 'payment_link.created',
12 | PAYMENT_LINK_PAID = 'payment_link.paid',
13 | PAYMENT_LINK_EXPIRED = 'payment_link.expired',
14 | }
15 |
16 | /**
17 | * Helper function to get default webhook events
18 | * @returns Array of default Nomod webhook events
19 | */
20 | export const getDefaultNomodWebhookEvents = (): NomodWebhookEvents[] => [
21 | NomodWebhookEvents.INVOICE_CREATED,
22 | NomodWebhookEvents.INVOICE_PAID,
23 | NomodWebhookEvents.INVOICE_FAILED,
24 | NomodWebhookEvents.PAYMENT_LINK_CREATED,
25 | NomodWebhookEvents.PAYMENT_LINK_PAID,
26 | NomodWebhookEvents.PAYMENT_LINK_EXPIRED,
27 | ];
28 |
--------------------------------------------------------------------------------
/src/utils/helpers/coupons.ts:
--------------------------------------------------------------------------------
1 | import { Coupon } from '@/models/Coupon';
2 |
3 | const filterValidCoupons = (coupons: Coupon[], currency?: string) => {
4 | const validCoupons = coupons.filter((coupon) => {
5 | if (coupon.redeem_after && coupon.redeem_before) {
6 | return new Date(coupon.redeem_after) <= new Date() && new Date(coupon.redeem_before) >= new Date();
7 | }
8 | return true;
9 | });
10 |
11 | const validCouponsWithRedemptions = validCoupons.filter((coupon) => {
12 | if (coupon.max_redemptions && coupon.max_redemptions > 0) {
13 | return coupon.total_redemptions < coupon.max_redemptions;
14 | }
15 | return true; // No redemption limit, so it's valid
16 | });
17 |
18 | // Filter by currency if provided
19 | const validCouponsWithCurrency = currency
20 | ? validCouponsWithRedemptions.filter((coupon) => coupon.currency.toLowerCase() === currency.toLowerCase())
21 | : validCouponsWithRedemptions;
22 |
23 | return validCouponsWithCurrency;
24 | };
25 |
26 | export default filterValidCoupons;
27 |
--------------------------------------------------------------------------------
/src/core/services/posthog/PosthogProvider.tsx:
--------------------------------------------------------------------------------
1 | // src/PosthogProvider.tsx
2 | import React, { ReactNode } from 'react';
3 | import { PostHogProvider } from 'posthog-js/react';
4 | import posthog from 'posthog-js';
5 | import PosthogErrorBoundary from './PosthogErrorBoundary';
6 | interface Props {
7 | children: ReactNode;
8 | }
9 |
10 | const isProd = import.meta.env.VITE_APP_ENVIRONMENT === 'prod';
11 |
12 | if (isProd) {
13 | posthog.init(import.meta.env.VITE_APP_PUBLIC_POSTHOG_KEY!, {
14 | api_host: import.meta.env.VITE_APP_PUBLIC_POSTHOG_HOST,
15 | capture_pageview: true,
16 | });
17 |
18 | // Safely start session recording
19 | posthog.sessionRecording?.startIfEnabledOrStop();
20 | }
21 |
22 | const PosthogWrapper: React.FC = ({ children }) => {
23 | if (isProd) {
24 | return (
25 |
26 | {children}
27 |
28 | );
29 | }
30 | return <>{children}>;
31 | };
32 |
33 | export default PosthogWrapper;
34 |
--------------------------------------------------------------------------------
/setup.ps1:
--------------------------------------------------------------------------------
1 | # Function to create .env file
2 | function Create-EnvFile {
3 | @"
4 | VITE_API_URL=https://api-dev.cloud.flexprice.io/v1
5 | VITE_ENVIRONMENT=self-hosted
6 | "@ | Out-File -FilePath ".env" -Encoding UTF8
7 | Write-Host "Created .env file with required configuration"
8 | }
9 |
10 | # Function to build and run Docker
11 | function Docker-BuildAndRun {
12 | Write-Host "Building Docker image..."
13 | docker build -t flexprice-front .
14 |
15 | Write-Host "Running Docker container..."
16 | docker run -d -p 3000:3000 flexprice-front
17 | }
18 |
19 | # Main execution
20 | Write-Host "Starting FlexPrice Frontend Setup..."
21 |
22 | # Create .env file
23 | Create-EnvFile
24 |
25 | # Check if Docker is installed
26 | if (!(Get-Command docker -ErrorAction SilentlyContinue)) {
27 | Write-Host "Docker is not installed. Please install Docker first."
28 | exit 1
29 | }
30 |
31 | # Build and run Docker
32 | Docker-BuildAndRun
33 |
34 | Write-Host "Setup completed! The application should be running at http://localhost:3000"
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as SliderPrimitive from '@radix-ui/react-slider';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Slider = React.forwardRef, React.ComponentPropsWithoutRef>(
7 | ({ className, ...props }, ref) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 | ),
15 | );
16 | Slider.displayName = SliderPrimitive.Root.displayName;
17 |
18 | export { Slider };
19 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
3 | import { Check } from 'lucide-react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
18 |
19 |
20 |
21 |
22 | ));
23 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
24 |
25 | export { Checkbox };
26 |
--------------------------------------------------------------------------------
/src/models/ScheduledTask.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export enum SCHEDULED_ENTITY_TYPE {
4 | EVENTS = 'events',
5 | INVOICE = 'invoice',
6 | CREDIT_TOPUPS = 'credit_topups',
7 | }
8 |
9 | export enum SCHEDULED_TASK_INTERVAL {
10 | HOURLY = 'hourly',
11 | DAILY = 'daily',
12 | }
13 |
14 | export type ScheduledEntityType = SCHEDULED_ENTITY_TYPE;
15 | export type ScheduledTaskInterval = SCHEDULED_TASK_INTERVAL;
16 |
17 | export interface ScheduledTask extends BaseModel {
18 | readonly connection_id: string;
19 | readonly entity_type: ScheduledEntityType;
20 | readonly interval: ScheduledTaskInterval;
21 | readonly enabled: boolean;
22 | readonly job_config: ScheduledTaskJobConfig;
23 | readonly last_run_at?: string;
24 | readonly next_run_at?: string;
25 | readonly last_run_status?: string;
26 | }
27 |
28 | export interface ScheduledTaskJobConfig {
29 | bucket: string;
30 | region: string;
31 | key_prefix: string;
32 | compression?: string;
33 | encryption?: string;
34 | max_file_size_mb?: number;
35 | endpoint_url?: string;
36 | use_path_style?: boolean;
37 | }
38 |
--------------------------------------------------------------------------------
/src/core/auth/AuthProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useEffect } from 'react';
2 | import { Navigate } from 'react-router';
3 | import { useUser } from '@/hooks/UserContext';
4 | import { PageLoader } from '@/components/atoms';
5 | import useUserhook from '@/hooks/useUser';
6 |
7 | interface AuthMiddlewareProps {
8 | children: ReactNode;
9 | requiredRole: string[];
10 | }
11 | const AuthMiddleware: React.FC = ({ children }) => {
12 | const userContext = useUser();
13 | const { user, loading, error } = useUserhook();
14 |
15 | useEffect(() => {
16 | if (user) {
17 | userContext.setUser(user);
18 | }
19 | }, [user, userContext]);
20 |
21 | if (loading) {
22 | return ;
23 | }
24 |
25 | if (error || !user) {
26 | return ;
27 | }
28 |
29 | // if (requiredRole && !requiredRole.includes(user.role)) {
30 | // return ;
31 | // }
32 |
33 | // Wrap children with AuthStateListener to handle auth state changes
34 | return {children}
;
35 | };
36 |
37 | export default AuthMiddleware;
38 |
--------------------------------------------------------------------------------
/src/models/Feature.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 | import { Meter } from './Meter';
3 |
4 | export enum AlertLevel {
5 | CRITICAL = 'critical',
6 | WARNING = 'warning',
7 | INFO = 'info',
8 | }
9 |
10 | export interface AlertThreshold {
11 | threshold: string;
12 | condition: 'above' | 'below';
13 | }
14 |
15 | export interface AlertSettings {
16 | critical?: AlertThreshold | null;
17 | warning?: AlertThreshold | null;
18 | info?: AlertThreshold | null;
19 | alert_enabled?: boolean;
20 | }
21 |
22 | export interface Feature extends BaseModel {
23 | readonly name: string;
24 | readonly description: string;
25 | readonly lookup_key?: string;
26 | readonly meter_id: string;
27 | readonly metadata: Metadata;
28 | readonly type: FEATURE_TYPE;
29 | readonly tenant_id: string;
30 | readonly unit_plural: string;
31 | readonly unit_singular: string;
32 | readonly meter?: Meter;
33 | readonly alert_settings?: AlertSettings;
34 | }
35 |
36 | export enum FEATURE_TYPE {
37 | METERED = 'metered',
38 | STATIC = 'static',
39 | BOOLEAN = 'boolean',
40 | }
41 |
42 | export default Feature;
43 |
--------------------------------------------------------------------------------
/src/hooks/UserContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
2 | import { logger } from '@/utils/common/Logger';
3 |
4 | interface UserProviderProps {
5 | children: ReactNode;
6 | }
7 |
8 | interface UserContextProp {
9 | user: any;
10 | setUser: (user: any) => void;
11 | }
12 | const UserContext = createContext({} as UserContextProp);
13 |
14 | export const UserProvider = ({ children }: UserProviderProps) => {
15 | const [user, setUser] = useState({});
16 |
17 | useEffect(() => {
18 | try {
19 | const userData = localStorage.getItem('user');
20 | if (userData) {
21 | const user = JSON.parse(userData);
22 | setUser(user);
23 | }
24 | } catch (error) {
25 | logger.error(error);
26 | // Clear invalid user data but don't trigger logout to prevent infinite redirects
27 | localStorage.removeItem('user');
28 | setUser(null);
29 | }
30 | }, []);
31 |
32 | return {children} ;
33 | };
34 |
35 | export const useUser = () => useContext(UserContext);
36 |
--------------------------------------------------------------------------------
/src/types/dto/Payment.ts:
--------------------------------------------------------------------------------
1 | import { Metadata, Pagination, Payment, PAYMENT_DESTINATION_TYPE, PAYMENT_METHOD_TYPE } from '@/models';
2 |
3 | export interface GetAllPaymentsPayload {
4 | currency?: string;
5 | destination_id?: string;
6 | destination_type?: string;
7 | end_time?: string;
8 | expand?: string;
9 | limit: number;
10 | offset: number;
11 | order?: 'asc' | 'desc';
12 | payment_gateway?: string;
13 | payment_ids?: string[];
14 | payment_method_type?: string;
15 | payment_status?: string;
16 | sort?: string;
17 | start_time?: string;
18 | status?: 'published' | 'deleted' | 'archived' | string;
19 | }
20 |
21 | export interface GetAllPaymentsResponse {
22 | items: Payment[];
23 | pagination: Pagination;
24 | }
25 |
26 | export interface RecordPaymentPayload {
27 | amount: number;
28 | currency: string;
29 | destination_id: string;
30 | destination_type: PAYMENT_DESTINATION_TYPE;
31 | idempotency_key?: string;
32 | metadata?: Metadata;
33 | payment_method_id?: string;
34 | payment_method_type: PAYMENT_METHOD_TYPE;
35 | payment_gateway?: string;
36 | process_payment?: boolean;
37 | recorded_at?: Date;
38 | }
39 |
--------------------------------------------------------------------------------
/src/types/dto/Coupon.ts:
--------------------------------------------------------------------------------
1 | import { COUPON_TYPE, COUPON_CADENCE, CouponRules } from '@/types/common/Coupon';
2 | import { QueryFilter } from './base';
3 | import { Coupon, Pagination, Metadata } from '@/models';
4 | import { TypedBackendFilter, TypedBackendSort } from '../formatters/QueryBuilder';
5 |
6 | export interface CreateCouponRequest {
7 | name: string;
8 | redeem_after?: string;
9 | redeem_before?: string;
10 | max_redemptions?: number;
11 | rules?: CouponRules;
12 | amount_off?: string;
13 | percentage_off?: string;
14 | type: COUPON_TYPE;
15 | cadence: COUPON_CADENCE;
16 | duration_in_periods?: number;
17 | metadata?: Metadata;
18 | currency?: string;
19 | }
20 |
21 | export interface UpdateCouponRequest {
22 | name?: string;
23 | metadata?: Metadata;
24 | }
25 |
26 | export interface GetCouponResponse {
27 | data: Coupon;
28 | }
29 |
30 | export interface ListCouponsResponse {
31 | items: Coupon[];
32 | pagination: Pagination;
33 | }
34 |
35 | export interface CouponFilter extends Omit {
36 | filters?: TypedBackendFilter[];
37 | sort?: TypedBackendSort[];
38 | coupon_ids?: string[];
39 | }
40 |
--------------------------------------------------------------------------------
/.github/workflows/demo.yaml:
--------------------------------------------------------------------------------
1 | name: Flexprice.io Demo Deployment
2 |
3 | env:
4 | VERCEL_ORG_ID: ${{secrets.VERCEL_ORG_ID}}
5 | VERCEL_PROJECT_ID: ${{secrets.VERCEL_PROJECT_ID_DEMO}}
6 | SENTRY_AUTH_TOKEN: ${{secrets.SENTRY_AUTH_TOKEN}}
7 |
8 | on:
9 | push:
10 | branches:
11 | - demo
12 |
13 | jobs:
14 | deploy-demo:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Install Vercel CLI
20 | run: npm install -g vercel
21 |
22 | - name: Pull Vercel Environment Information
23 | run: vercel pull --yes --environment=production --token=${{secrets.VERCEL_TOKEN}}
24 |
25 | - name: Increase Node.js Memory Limit
26 | run: |
27 | export NODE_OPTIONS="--max-old-space-size=4096"
28 | npm install
29 |
30 | - name: Build Project Artifacts
31 | run: |
32 | export NODE_OPTIONS="--max-old-space-size=4096"
33 | vercel build --prod --token=${{secrets.VERCEL_TOKEN}}
34 |
35 | - name: Deploy Project Artifacts
36 | run: vercel deploy --prebuilt --prod --token=${{secrets.VERCEL_TOKEN}}
37 |
--------------------------------------------------------------------------------
/src/types/dto/CostSheet.ts:
--------------------------------------------------------------------------------
1 | import { Pagination, CostSheet, Metadata, Price } from '@/models';
2 | import { TypedBackendFilter, TypedBackendSort } from '../formatters/QueryBuilder';
3 |
4 | export interface CreateCostSheetRequest {
5 | name: string;
6 | lookup_key: string;
7 | description?: string;
8 | metadata?: Metadata;
9 | }
10 |
11 | export interface CostSheetResponse extends CostSheet {
12 | prices: Price[];
13 | }
14 |
15 | export interface UpdateCostSheetRequest {
16 | name?: string;
17 | description?: string;
18 | metadata?: Metadata;
19 | }
20 |
21 | export interface GetCostSheetsPayload {
22 | end_time?: string;
23 | expand?: string;
24 | cost_sheet_ids?: string[];
25 | limit?: number;
26 | lookup_key?: string;
27 | offset?: number;
28 | order?: string;
29 | sort?: string;
30 | start_time?: string;
31 | status?: string;
32 | lookup_keys?: string[];
33 | }
34 |
35 | export interface GetCostSheetsResponse {
36 | items: CostSheetResponse[];
37 | pagination: Pagination;
38 | }
39 |
40 | export interface GetCostSheetsByFilterPayload extends Pagination {
41 | filters: TypedBackendFilter[];
42 | sort: TypedBackendSort[];
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/staging.yaml:
--------------------------------------------------------------------------------
1 | name: Flexprice.io Staging Deployment
2 |
3 | env:
4 | VERCEL_ORG_ID: ${{secrets.VERCEL_ORG_ID}}
5 | VERCEL_PROJECT_ID: ${{secrets.VERCEL_PROJECT_ID_STAGING}}
6 | SENTRY_AUTH_TOKEN: ${{secrets.SENTRY_AUTH_TOKEN}}
7 |
8 | on:
9 | push:
10 | branches:
11 | - staging
12 |
13 | jobs:
14 | deploy-staging:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Install Vercel CLI
20 | run: npm install -g vercel
21 |
22 | - name: Pull Vercel Environment Information
23 | run: vercel pull --yes --environment=production --token=${{secrets.VERCEL_TOKEN}}
24 |
25 | - name: Increase Node.js Memory Limit
26 | run: |
27 | export NODE_OPTIONS="--max-old-space-size=4096"
28 | npm install
29 |
30 | - name: Build Project Artifacts
31 | run: |
32 | export NODE_OPTIONS="--max-old-space-size=4096"
33 | vercel build --prod --token=${{secrets.VERCEL_TOKEN}}
34 |
35 | - name: Deploy Project Artifacts
36 | run: vercel deploy --prebuilt --prod --token=${{secrets.VERCEL_TOKEN}}
37 |
--------------------------------------------------------------------------------
/src/hooks/useBreadcrumbs.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocation } from 'react-router';
3 | import { useBreadcrumbsStore } from '@/store/useBreadcrumbsStore';
4 |
5 | export interface BreadcrumbItem {
6 | label: string;
7 | path: string;
8 | }
9 |
10 | const formatPathSegment = (segment: string): string => {
11 | return segment
12 | .replace(/-/g, ' ')
13 | .split(' ')
14 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
15 | .join(' ');
16 | };
17 |
18 | export const useBreadcrumbs = () => {
19 | const location = useLocation();
20 | const { setBreadcrumbs, setLoading } = useBreadcrumbsStore();
21 |
22 | useEffect(() => {
23 | setLoading(true);
24 | const pathSegments = location.pathname.split('/').filter(Boolean);
25 |
26 | const newBreadcrumbs = pathSegments.map((segment, index, arr) => {
27 | const path = `/${arr.slice(0, index + 1).join('/')}`;
28 |
29 | const label = formatPathSegment(segment);
30 |
31 | return {
32 | label,
33 | path,
34 | };
35 | });
36 |
37 | setBreadcrumbs(newBreadcrumbs);
38 | setLoading(false);
39 | }, [location.pathname, setBreadcrumbs, setLoading]);
40 | };
41 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider } from 'react-router';
2 | import { MainRouter } from '@/core/routes/Routes';
3 | import { UserProvider } from '@/hooks/UserContext';
4 | import { Toaster } from 'react-hot-toast';
5 | import { DocsProvider } from './context/DocsContext';
6 | import ReactQueryProvider from './core/services/tanstack/ReactQueryProvider';
7 | import useVersionCheck from '@/hooks/useVersionCheck';
8 |
9 | const App = () => {
10 | useVersionCheck();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 | {/* Toast Notifications */}
20 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/src/api/AuthApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { LoginData, LocalUser, SignupData } from '@/types/dto';
3 |
4 | class AuthApi {
5 | private static baseUrl = '/auth';
6 |
7 | public static async Login(email: string, password: string) {
8 | return await AxiosClient.post(`${this.baseUrl}/login`, { email, password } as LoginData);
9 | }
10 |
11 | public static async Signup(data: SignupData) {
12 | return await AxiosClient.post(`${this.baseUrl}/signup`, data);
13 | }
14 |
15 | public static async Logout() {
16 | return await AxiosClient.post(`${this.baseUrl}/logout`);
17 | }
18 |
19 | public static async VerifyEmail(token: string) {
20 | return await AxiosClient.post(`${this.baseUrl}/signup/confirmation`, { token });
21 | }
22 |
23 | public static async ResetPassword(token: string, newPassword: string) {
24 | return await AxiosClient.post(`${this.baseUrl}/reset-password`, { token, newPassword });
25 | }
26 |
27 | public static async ResendVerificationEmail(email: string) {
28 | return await AxiosClient.post(`${this.baseUrl}/resend-verification`, { email });
29 | }
30 | }
31 |
32 | export default AuthApi;
33 |
--------------------------------------------------------------------------------
/src/types/dto/CreditGrantApplication.ts:
--------------------------------------------------------------------------------
1 | import { CreditGrantApplication, APPLICATION_STATUS } from '@/models';
2 | import { QueryFilter, TimeRangeFilter } from './base';
3 | import { Pagination } from '@/models';
4 |
5 | // ============================================
6 | // Credit Grant Application Response Types
7 | // ============================================
8 |
9 | export type CreditGrantApplicationResponse = CreditGrantApplication;
10 |
11 | export interface ListCreditGrantApplicationsResponse extends Pagination {
12 | items: CreditGrantApplicationResponse[];
13 | }
14 |
15 | // ============================================
16 | // Credit Grant Application Filter Types
17 | // ============================================
18 |
19 | export interface GetCreditGrantApplicationsRequest extends QueryFilter, TimeRangeFilter {
20 | application_ids?: string[];
21 | credit_grant_ids?: string[];
22 | subscription_ids?: string[];
23 | scheduled_for?: string;
24 | applied_at?: string;
25 | application_statuses?: APPLICATION_STATUS[];
26 | }
27 |
28 | export interface GetUpcomingCreditGrantApplicationsRequest extends QueryFilter, TimeRangeFilter {
29 | subscription_ids?: string[];
30 | }
31 |
--------------------------------------------------------------------------------
/src/core/services/supbase/config.ts:
--------------------------------------------------------------------------------
1 | import { NODE_ENV, NodeEnv } from '@/types';
2 | import { createClient } from '@supabase/supabase-js';
3 |
4 | const isSelfHosted = NODE_ENV === NodeEnv.SELF_HOSTED;
5 | // Create a mock client for self-hosted mode
6 | const createMockClient = () => {
7 | return {
8 | auth: {
9 | signIn: async () => ({ user: null, error: null }),
10 | signOut: async () => ({ error: null }),
11 | onAuthStateChange: () => ({ data: null, error: null }),
12 | getSession: async () => ({ data: null, error: null }),
13 | },
14 | from: () => ({
15 | select: async () => [],
16 | insert: async () => ({ data: null, error: null }),
17 | update: async () => ({ data: null, error: null }),
18 | delete: async () => ({ data: null, error: null }),
19 | }),
20 | };
21 | };
22 |
23 | // Use real Supabase client only if not in self-hosted mode
24 | const supabaseUrl = isSelfHosted ? '' : import.meta.env.VITE_SUPABASE_URL || '';
25 | const supabaseKey = isSelfHosted ? '' : import.meta.env.VITE_SUPABASE_ANON_KEY || '';
26 |
27 | const supabase = isSelfHosted ? (createMockClient() as any) : createClient(supabaseUrl, supabaseKey);
28 |
29 | export default supabase;
30 |
--------------------------------------------------------------------------------
/src/models/Meter.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 |
3 | export interface Meter extends BaseModel {
4 | readonly aggregation: {
5 | field: string;
6 | type: METER_AGGREGATION_TYPE;
7 | multiplier?: number;
8 | bucket_size?: BUCKET_SIZE;
9 | };
10 | readonly event_name: string;
11 | readonly filters: Array<{
12 | key: string;
13 | values: string[];
14 | }>;
15 | readonly name: string;
16 | readonly reset_usage: METER_USAGE_RESET_PERIOD;
17 | }
18 |
19 | export enum METER_USAGE_RESET_PERIOD {
20 | NEVER = 'NEVER',
21 | BILLING_PERIOD = 'BILLING_PERIOD',
22 | }
23 |
24 | export enum METER_AGGREGATION_TYPE {
25 | SUM = 'SUM',
26 | COUNT = 'COUNT',
27 | COUNT_UNIQUE = 'COUNT_UNIQUE',
28 | LATEST = 'LATEST',
29 | SUM_WITH_MULTIPLIER = 'SUM_WITH_MULTIPLIER',
30 | MAX = 'MAX',
31 | WEIGHTED_SUM = 'WEIGHTED_SUM',
32 | AVG = 'AVG',
33 | }
34 |
35 | export enum BUCKET_SIZE {
36 | WindowSizeMinute = 'MINUTE',
37 | WindowSize15Min = '15MIN',
38 | WindowSize30Min = '30MIN',
39 | WindowSizeHour = 'HOUR',
40 | WindowSize3Hour = '3HOUR',
41 | WindowSize6Hour = '6HOUR',
42 | WindowSize12Hour = '12HOUR',
43 | WindowSizeDay = 'DAY',
44 | WindowSizeWeek = 'WEEK',
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const badgeVariants = cva(
7 | 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8 | {
9 | variants: {
10 | variant: {
11 | default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
12 | secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
13 | destructive: 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
14 | outline: 'text-foreground',
15 | },
16 | },
17 | defaultVariants: {
18 | variant: 'default',
19 | },
20 | },
21 | );
22 |
23 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
24 |
25 | function Badge({ className, variant, ...props }: BadgeProps) {
26 | return
;
27 | }
28 |
29 | export { Badge, badgeVariants };
30 |
--------------------------------------------------------------------------------
/src/components/molecules/CustomerSubscription/SubscriptionPauseWarning.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@/components/atoms';
2 | import { AlertCircle } from 'lucide-react';
3 | import { formatDateShort } from '@/utils/common/helper_functions';
4 |
5 | interface SubscriptionPauseWarningProps {
6 | pauseStartDate: string;
7 | pauseEndDate: string;
8 | resumeDate: string;
9 | }
10 |
11 | const SubscriptionPauseWarning = ({ pauseStartDate, pauseEndDate, resumeDate }: SubscriptionPauseWarningProps) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
The subscription is paused
18 |
19 | The subscription will be paused from {formatDateShort(pauseStartDate)} to {formatDateShort(pauseEndDate)}. The subscription will
20 | resume from {formatDateShort(resumeDate)} and the customer will not be charged until {formatDateShort(pauseEndDate)}.
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default SubscriptionPauseWarning;
29 |
--------------------------------------------------------------------------------
/src/types/dto/Meter.ts:
--------------------------------------------------------------------------------
1 | import { Meter, METER_AGGREGATION_TYPE, METER_USAGE_RESET_PERIOD, Pagination, BUCKET_SIZE } from '@/models';
2 |
3 | // ============================================
4 | // Meter Request Types
5 | // ============================================
6 |
7 | export interface MeterFilter {
8 | key: string;
9 | values: string[];
10 | }
11 |
12 | export interface MeterAggregation {
13 | type: METER_AGGREGATION_TYPE;
14 | field?: string;
15 | multiplier?: number;
16 | bucket_size?: BUCKET_SIZE;
17 | }
18 |
19 | export interface CreateMeterRequest {
20 | name: string;
21 | event_name: string;
22 | aggregation: MeterAggregation;
23 | reset_usage: METER_USAGE_RESET_PERIOD;
24 | filters?: MeterFilter[];
25 | }
26 |
27 | export interface UpdateMeterRequest {
28 | filters?: MeterFilter[];
29 | }
30 |
31 | // ============================================
32 | // Meter Response Types
33 | // ============================================
34 |
35 | export type MeterResponse = Meter;
36 |
37 | export interface GetAllMetersResponse {
38 | items: Meter[];
39 | pagination: Pagination;
40 | }
41 |
42 | export interface ListMetersResponse {
43 | items: MeterResponse[];
44 | pagination: Pagination;
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/organisms/Subscription/UsageTable.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { SubscriptionUsage } from '@/models/Subscription';
3 | import { ColumnData, FlexpriceTable } from '@/components/molecules';
4 | import { FormHeader } from '@/components/atoms';
5 |
6 | export interface UsageTableProps {
7 | data: SubscriptionUsage;
8 | }
9 |
10 | const UsageTable: FC = ({ data }) => {
11 | const mappedData = (data?.charges ?? []).map((usage) => ({
12 | name: usage.meter_display_name,
13 | quantity: usage.quantity,
14 | amount: usage.display_amount,
15 | }));
16 |
17 | const columns: ColumnData[] = [
18 | {
19 | fieldName: 'name',
20 | title: 'Feature Name',
21 | },
22 | {
23 | fieldName: 'quantity',
24 | title: 'Quantity',
25 | },
26 | {
27 | fieldName: 'amount',
28 | title: 'Amount',
29 | },
30 | ];
31 |
32 | return (
33 |
39 | );
40 | };
41 |
42 | export default UsageTable;
43 |
--------------------------------------------------------------------------------
/.github/workflows/production.yaml:
--------------------------------------------------------------------------------
1 | name: Flexprice.io Production Deployment
2 |
3 | env:
4 | VERCEL_ORG_ID: ${{secrets.VERCEL_ORG_ID}}
5 | VERCEL_PROJECT_ID: ${{secrets.VERCEL_PROJECT_ID}}
6 | SENTRY_AUTH_TOKEN: ${{secrets.SENTRY_AUTH_TOKEN}}
7 |
8 | on:
9 | push:
10 | branches:
11 | - main
12 |
13 | jobs:
14 | deploy-production:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Install Vercel CLI
20 | run: npm install -g vercel
21 |
22 | - name: Pull Vercel Environment Information
23 | run: vercel pull --yes --environment=production --token=${{secrets.VERCEL_TOKEN}}
24 |
25 | - name: Increase Node.js Memory Limit
26 | run: |
27 | export NODE_OPTIONS="--max-old-space-size=4096"
28 | npm install
29 |
30 | - name: Generate Version Meta
31 | run: npm run generate-meta
32 |
33 | - name: Build Project Artifacts
34 | run: |
35 | export NODE_OPTIONS="--max-old-space-size=4096"
36 | vercel build --prod --token=${{secrets.VERCEL_TOKEN}}
37 |
38 | - name: Deploy Project Artifacts
39 | run: vercel deploy --prebuilt --prod --token=${{secrets.VERCEL_TOKEN}}
40 |
--------------------------------------------------------------------------------
/src/pages/product-catalog/index.ts:
--------------------------------------------------------------------------------
1 | // Addons
2 | export { default as AddonCharges } from './addons/AddonCharges';
3 | export { default as AddonDetails } from './addons/AddonDetails';
4 | export { default as Addons } from './addons/Addons';
5 |
6 | // Cost Sheets
7 | export { default as CostSheetCharges } from './cost-sheets/CostSheetCharges';
8 | export { default as CostSheetDetails } from './cost-sheets/CostSheetDetails';
9 | export { default as CostSheets } from './cost-sheets/CostSheets';
10 |
11 | // Coupons
12 | export { default as CouponDetails } from './coupons/CouponDetails';
13 | export { default as Coupons } from './coupons/Coupons';
14 |
15 | // Features
16 | export { default as AddFeature } from './features/AddFeature';
17 | export { default as FeatureDetails } from './features/FeatureDetails';
18 | export { default as Features } from './features/Features';
19 |
20 | // Plans
21 | export { default as AddCharges } from './plans/AddCharges';
22 | export { default as PlanDetailsPage, formatInvoiceCadence } from './plans/PlanDetailsPage';
23 | export { default as Plans } from './plans/Plans';
24 | export { default as Pricing, PlanType } from './plans/Pricing';
25 |
26 | // Groups
27 | export { default as Groups } from './groups/Groups';
28 |
--------------------------------------------------------------------------------
/src/components/molecules/Table/TooltipCell.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react';
2 | import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
3 | import { Copy } from 'lucide-react';
4 | import { toast } from 'react-hot-toast';
5 |
6 | interface Props {
7 | tooltipContent: ReactNode;
8 | tooltipText: string;
9 | }
10 | const TooltipCell: FC = ({ tooltipContent, tooltipText }) => {
11 | const copyToClipboard = () => {
12 | navigator.clipboard.writeText(tooltipText);
13 | toast.success('Copied to clipboard');
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | {tooltipContent || '--'}
22 |
26 |
27 |
28 |
29 | {tooltipText}
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default TooltipCell;
37 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider;
7 |
8 | const Tooltip = TooltipPrimitive.Root;
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger;
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ));
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
29 |
--------------------------------------------------------------------------------
/src/components/atoms/Toggle/Toggle.tsx:
--------------------------------------------------------------------------------
1 | import { Label } from '@/components/ui/label';
2 | import { FormHeader, Spacer } from '..';
3 | import { Switch } from '@/components/ui/switch';
4 | import { FC } from 'react';
5 | interface Props {
6 | onChange: (value: boolean) => void;
7 | checked: boolean;
8 | title?: string;
9 | label?: string;
10 | description?: string;
11 | error?: string;
12 | disabled?: boolean;
13 | className?: string;
14 | }
15 |
16 | const Toggle: FC = ({ onChange, checked, description, error, label, title, disabled, className }) => {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 | {label}
24 |
25 | {description}
26 |
27 |
28 | {error &&
{error}
}
29 |
30 | );
31 | };
32 |
33 | export default Toggle;
34 |
--------------------------------------------------------------------------------
/src/api/CreditGrantApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { generateQueryParams } from '@/utils/common/api_helper';
3 | import {
4 | CreateCreditGrantRequest,
5 | UpdateCreditGrantRequest,
6 | CreditGrantResponse,
7 | ListCreditGrantsResponse,
8 | GetCreditGrantsRequest,
9 | } from '@/types/dto';
10 |
11 | class CreditGrantApi {
12 | private static baseUrl = '/creditgrants';
13 |
14 | public static async Create(data: CreateCreditGrantRequest) {
15 | return AxiosClient.post(this.baseUrl, data);
16 | }
17 |
18 | public static async List(data: GetCreditGrantsRequest) {
19 | const url = generateQueryParams(this.baseUrl, data);
20 | return await AxiosClient.get(url);
21 | }
22 |
23 | public static async Update(id: string, data: UpdateCreditGrantRequest) {
24 | return await AxiosClient.put(`${this.baseUrl}/${id}`, data);
25 | }
26 |
27 | public static async Delete(id: string) {
28 | return await AxiosClient.delete(`${this.baseUrl}/${id}`);
29 | }
30 |
31 | public static async Get(id: string) {
32 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
33 | }
34 | }
35 |
36 | export default CreditGrantApi;
37 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as SwitchPrimitives from '@radix-ui/react-switch';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
20 |
26 |
27 | ));
28 | Switch.displayName = SwitchPrimitives.Root.displayName;
29 |
30 | export { Switch };
31 |
--------------------------------------------------------------------------------
/src/pages/insights-tools/exports/TaskRunsPage.tsx:
--------------------------------------------------------------------------------
1 | import { Page, Button } from '@/components/atoms';
2 | import { useParams, useNavigate } from 'react-router';
3 | import { ArrowLeft } from 'lucide-react';
4 | import TaskRunsTable from '@/components/molecules/TaskRunsTable/TaskRunsTable';
5 | import { ApiDocsContent } from '@/components/molecules';
6 | import { RouteNames } from '@/core/routes/Routes';
7 |
8 | const TaskRunsPage = () => {
9 | const { connectionId, exportId } = useParams<{ connectionId: string; exportId: string }>();
10 | const navigate = useNavigate();
11 |
12 | return (
13 |
14 |
15 |
16 | {/* Back button */}
17 |
18 |
navigate(RouteNames.s3ExportDetails.replace(':connectionId', connectionId!).replace(':exportId', exportId!))}
21 | className='flex items-center gap-2'>
22 |
23 | Back to Export Details
24 |
25 |
26 |
27 | {/* Task Runs Table */}
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default TaskRunsPage;
36 |
--------------------------------------------------------------------------------
/src/models/CustomerUsage.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base';
2 | import Feature from './Feature';
3 |
4 | // Entity type as returned from backend (lowercase)
5 | export enum ENTITLEMENT_SOURCE_ENTITY_TYPE {
6 | PLAN = 'plan',
7 | ADDON = 'addon',
8 | SUBSCRIPTION = 'subscription',
9 | }
10 |
11 | interface CustomerUsage extends BaseModel {
12 | readonly feature: Feature;
13 | readonly total_limit: number | null;
14 | readonly is_unlimited: boolean;
15 | readonly current_usage: number;
16 | readonly usage_percent: number;
17 | readonly is_enabled: boolean;
18 | readonly is_soft_limit: boolean;
19 | readonly next_usage_reset_at: string | null;
20 | readonly sources: EntitlementSource[];
21 | }
22 |
23 | export interface EntitlementSource {
24 | readonly subscription_id: string;
25 | readonly entity_id: string;
26 | readonly entity_type: ENTITLEMENT_SOURCE_ENTITY_TYPE;
27 | readonly quantity: number;
28 | readonly entity_name: string;
29 | readonly entitlement_id: string;
30 | readonly is_enabled: boolean;
31 | readonly usage_limit: number | null;
32 | readonly static_value: string | null;
33 | readonly usage_reset_period: string | null;
34 | // Legacy fields for backward compatibility
35 | readonly plan_id?: string;
36 | readonly plan_name?: string;
37 | }
38 |
39 | export default CustomerUsage;
40 |
--------------------------------------------------------------------------------
/src/models/WalletBalance.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 | import { WALLET_AUTO_TOPUP_TRIGGER, WALLET_STATUS, WALLET_TYPE } from './Wallet';
3 |
4 | export interface WalletBalance extends BaseModel {
5 | readonly balance: number;
6 | readonly currency: string;
7 | readonly customer_id: string;
8 | readonly description: string;
9 | readonly metadata: Metadata;
10 | readonly real_time_balance: number;
11 | readonly wallet_status: WALLET_STATUS;
12 | }
13 |
14 | export interface RealtimeWalletBalance extends BaseModel {
15 | readonly customer_id: string;
16 | readonly currency: string;
17 | readonly balance: string;
18 | readonly credit_balance: string;
19 | readonly wallet_status: WALLET_STATUS;
20 | readonly name: string;
21 | readonly description: string;
22 | readonly metadata: Metadata;
23 | readonly auto_topup_trigger: WALLET_AUTO_TOPUP_TRIGGER;
24 | readonly auto_topup_min_balance: string;
25 | readonly auto_topup_amount: string;
26 | readonly wallet_type: WALLET_TYPE;
27 | readonly config: {
28 | readonly allowed_price_types: readonly string[];
29 | };
30 | readonly conversion_rate: string;
31 | readonly environment_id: string;
32 | readonly real_time_balance: string;
33 | readonly real_time_credit_balance: string;
34 | readonly unpaid_invoice_amount: string;
35 | readonly current_period_usage: string;
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/atoms/Progress/Progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ProgressPrimitive from '@radix-ui/react-progress';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | interface CustomProgressProps extends React.ComponentPropsWithoutRef {
7 | indicatorColor?: string;
8 | backgroundColor?: string;
9 | label?: React.ReactNode;
10 | labelColor?: string;
11 | className?: string;
12 | }
13 | const Progress = React.forwardRef, CustomProgressProps>(
14 | ({ className, value, indicatorColor, label, backgroundColor, labelColor, ...props }, ref) => (
15 |
16 |
20 |
24 |
25 |
{label}
26 |
27 | ),
28 | );
29 | Progress.displayName = ProgressPrimitive.Root.displayName;
30 |
31 | export default Progress;
32 |
--------------------------------------------------------------------------------
/src/models/CreditGrant.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel, Metadata } from './base';
2 |
3 | export enum CREDIT_GRANT_SCOPE {
4 | PLAN = 'PLAN',
5 | SUBSCRIPTION = 'SUBSCRIPTION',
6 | }
7 |
8 | export interface CreditGrant extends BaseModel {
9 | readonly credits: number;
10 | readonly cadence: CREDIT_GRANT_CADENCE;
11 | readonly metadata: Metadata;
12 | readonly name: string;
13 | readonly period?: CREDIT_GRANT_PERIOD;
14 | readonly period_count?: number;
15 | readonly plan_id?: string;
16 | readonly priority?: number;
17 | readonly scope: CREDIT_GRANT_SCOPE;
18 | readonly expiration_duration?: number;
19 | readonly expiration_type?: CREDIT_GRANT_EXPIRATION_TYPE;
20 | readonly expiration_duration_unit?: CREDIT_GRANT_PERIOD_UNIT;
21 | readonly subscription_id?: string;
22 | }
23 |
24 | export enum CREDIT_GRANT_CADENCE {
25 | ONETIME = 'ONETIME',
26 | RECURRING = 'RECURRING',
27 | }
28 |
29 | export enum CREDIT_GRANT_EXPIRATION_TYPE {
30 | NEVER = 'NEVER',
31 | DURATION = 'DURATION',
32 | BILLING_CYCLE = 'BILLING_CYCLE',
33 | }
34 |
35 | export enum CREDIT_GRANT_PERIOD_UNIT {
36 | DAYS = 'DAY',
37 | WEEKS = 'WEEK',
38 | MONTHS = 'MONTH',
39 | YEARS = 'YEAR',
40 | }
41 |
42 | export enum CREDIT_GRANT_PERIOD {
43 | DAILY = 'DAILY',
44 | WEEKLY = 'WEEKLY',
45 | MONTHLY = 'MONTHLY',
46 | ANNUAL = 'ANNUAL',
47 | QUARTERLY = 'QUARTERLY',
48 | HALF_YEARLY = 'HALF_YEARLY',
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/common/credit_grant_helpers.ts:
--------------------------------------------------------------------------------
1 | import { CREDIT_GRANT_EXPIRATION_TYPE, CreditGrant } from '@/models/CreditGrant';
2 |
3 | export const formatExpirationType = (expirationType: CREDIT_GRANT_EXPIRATION_TYPE) => {
4 | switch (expirationType) {
5 | case CREDIT_GRANT_EXPIRATION_TYPE.DURATION:
6 | return 'Days';
7 | case CREDIT_GRANT_EXPIRATION_TYPE.BILLING_CYCLE:
8 | return 'Subscription period';
9 | case CREDIT_GRANT_EXPIRATION_TYPE.NEVER:
10 | return '--';
11 | default:
12 | return '--';
13 | }
14 | };
15 |
16 | export const formatExpirationPeriod = (grant: CreditGrant): string => {
17 | if (
18 | grant.expiration_type === CREDIT_GRANT_EXPIRATION_TYPE.DURATION &&
19 | grant.expiration_duration !== null &&
20 | grant.expiration_duration !== undefined &&
21 | grant.expiration_duration_unit
22 | ) {
23 | const duration = grant.expiration_duration;
24 | const unit = grant.expiration_duration_unit.toLowerCase();
25 |
26 | // Convert plural unit names to singular when duration is 1, and handle pluralization
27 | let unitName = unit.endsWith('s') ? unit.slice(0, -1) : unit; // Remove 's' from 'days', 'weeks', etc.
28 | if (duration !== 1) {
29 | unitName += 's'; // Add 's' back for plural
30 | }
31 |
32 | return `${duration} ${unitName}`;
33 | }
34 |
35 | return grant.expiration_type ? formatExpirationType(grant.expiration_type) : '--';
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/atoms/Dialog/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import { Dialog as ShadcnDialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
2 | import { cn } from '@/lib/utils';
3 | import { FC, ReactNode } from 'react';
4 |
5 | interface Props {
6 | isOpen: boolean;
7 | onOpenChange: (isOpen: boolean) => void;
8 | title: string;
9 | description?: string;
10 | children?: ReactNode;
11 | className?: string;
12 | titleClassName?: string;
13 | descriptionClassName?: string;
14 | showCloseButton?: boolean;
15 | }
16 |
17 | const Dialog: FC = ({
18 | className,
19 | isOpen,
20 | onOpenChange,
21 | title,
22 | description,
23 | children,
24 | titleClassName,
25 | descriptionClassName,
26 | showCloseButton = true,
27 | }) => {
28 | return (
29 |
30 |
31 |
32 | {title}
33 | {description && {description} }
34 |
35 | {children}
36 |
37 |
38 | );
39 | };
40 |
41 | export default Dialog;
42 |
--------------------------------------------------------------------------------
/src/components/atoms/Modal/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import { X } from 'lucide-react';
3 | import { FC, ReactNode } from 'react';
4 | import { createPortal } from 'react-dom';
5 | import { Button } from '../Button';
6 |
7 | interface ModalProps {
8 | isOpen: boolean;
9 | onOpenChange: (open: boolean) => void;
10 | children?: ReactNode;
11 | className?: string;
12 | showOverlay?: boolean;
13 | }
14 |
15 | const Modal: FC = ({ isOpen, onOpenChange, children, className, showOverlay = true }) => {
16 | if (!isOpen) return null;
17 |
18 | const modalContent = (
19 | onOpenChange(false)}>
22 |
e.stopPropagation()}>
23 | {
27 | e.stopPropagation();
28 | onOpenChange(false);
29 | }}>
30 |
31 |
32 | {children}
33 |
34 |
35 | );
36 |
37 | // Render into portal
38 | const modalRoot = document.getElementById('modal-root');
39 | if (!modalRoot) return null;
40 |
41 | return createPortal(modalContent, modalRoot);
42 | };
43 |
44 | export default Modal;
45 |
--------------------------------------------------------------------------------
/src/components/atoms/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Tooltip as TooltipRoot, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
3 | import { cn } from '@/lib/utils';
4 |
5 | interface TooltipProps {
6 | /** The element that triggers the tooltip */
7 | children: ReactNode;
8 | /** The content to display in the tooltip */
9 | content: ReactNode;
10 | /** Delay before showing tooltip in ms */
11 | delayDuration?: number;
12 | /** Side of the trigger to show tooltip */
13 | side?: 'top' | 'right' | 'bottom' | 'left';
14 | /** Alignment of the tooltip */
15 | align?: 'start' | 'center' | 'end';
16 | /** Offset from the trigger */
17 | sideOffset?: number;
18 | /** Custom className for the tooltip content */
19 | className?: string;
20 | }
21 |
22 | const Tooltip: React.FC = ({
23 | children,
24 | content,
25 | delayDuration,
26 | side = 'top',
27 | align = 'center',
28 | sideOffset = 4,
29 | className,
30 | }) => {
31 | return (
32 |
33 |
34 | {children}
35 |
36 | {content}
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default Tooltip;
44 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as PopoverPrimitive from '@radix-ui/react-popover';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Popover = PopoverPrimitive.Root;
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger;
9 |
10 | const PopoverAnchor = PopoverPrimitive.Anchor;
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ));
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
30 |
31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
32 |
--------------------------------------------------------------------------------
/src/components/atoms/Page/Page.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import { SectionHeader } from '@/components/atoms';
3 | import { FC, useEffect } from 'react';
4 |
5 | interface Props {
6 | children?: React.ReactNode;
7 | className?: string;
8 | type?: 'left-aligned' | 'default';
9 | header?: React.ReactNode;
10 | heading?: string | React.ReactNode;
11 | headingClassName?: string;
12 | headingCTA?: React.ReactNode;
13 | }
14 |
15 | const Page: FC = ({ children, className, type = 'default', header, heading, headingClassName, headingCTA }) => {
16 | if (heading && header) {
17 | throw new Error('You cannot pass both heading and header props');
18 | }
19 |
20 | useEffect(() => {
21 | if (heading) {
22 | document.title = `${heading} | Flexprice`;
23 | }
24 | }, [heading]);
25 |
26 | return (
27 |
28 |
35 | {header && header}
36 | {heading && (
37 |
38 | {headingCTA}
39 |
40 | )}
41 |
{children}
42 |
43 |
44 | );
45 | };
46 |
47 | export default Page;
48 |
--------------------------------------------------------------------------------
/src/api/EnvironmentApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { ACTIVE_ENVIRONMENT_ID_KEY } from '@/hooks/useEnvironment';
3 | import { Environment } from '@/models';
4 | import { CreateEnvironmentPayload, ListEnvironmentResponse } from '@/types/dto';
5 |
6 | class EnvironmentApi {
7 | private static baseUrl = '/environments';
8 |
9 | // API Methods
10 | public static async getAllEnvironments(): Promise {
11 | try {
12 | return await AxiosClient.get(this.baseUrl);
13 | } catch (error) {
14 | return { environments: [], total: 0 } as ListEnvironmentResponse;
15 | }
16 | }
17 |
18 | public static async getEnvironmentById(id: string): Promise {
19 | try {
20 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
21 | } catch (error) {
22 | return null;
23 | }
24 | }
25 |
26 | public static async createEnvironment(payload: CreateEnvironmentPayload): Promise {
27 | return await AxiosClient.post(this.baseUrl, payload);
28 | }
29 |
30 | public static getActiveEnvironmentId(): string | null {
31 | return localStorage.getItem(ACTIVE_ENVIRONMENT_ID_KEY);
32 | }
33 |
34 | public static setActiveEnvironmentId(environmentId: string): void {
35 | localStorage.setItem(ACTIVE_ENVIRONMENT_ID_KEY, environmentId);
36 | }
37 | }
38 |
39 | export default EnvironmentApi;
40 |
--------------------------------------------------------------------------------
/src/api/CouponApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { Coupon, Pagination } from '@/models';
3 | import { CreateCouponRequest, UpdateCouponRequest, ListCouponsResponse, CouponFilter } from '@/types/dto';
4 | import { generateQueryParams } from '@/utils/common/api_helper';
5 |
6 | class CouponApi {
7 | private static baseUrl = '/coupons';
8 |
9 | public static async createCoupon(payload: CreateCouponRequest): Promise {
10 | return await AxiosClient.post(`${this.baseUrl}`, payload);
11 | }
12 |
13 | public static async getCouponById(id: string): Promise {
14 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
15 | }
16 |
17 | public static async updateCoupon(id: string, payload: UpdateCouponRequest): Promise {
18 | return await AxiosClient.put(`${this.baseUrl}/${id}`, payload);
19 | }
20 |
21 | public static async deleteCoupon(id: string): Promise {
22 | return await AxiosClient.delete(`${this.baseUrl}/${id}`);
23 | }
24 |
25 | public static async getAllCoupons({ limit = 10, offset = 0 }: Pagination): Promise {
26 | const url = generateQueryParams(this.baseUrl, { limit, offset });
27 | return await AxiosClient.get(url);
28 | }
29 |
30 | public static async getCouponsByFilters(payload: CouponFilter): Promise {
31 | return await AxiosClient.post(`${this.baseUrl}/search`, payload);
32 | }
33 | }
34 |
35 | export default CouponApi;
36 |
--------------------------------------------------------------------------------
/src/components/molecules/InvoiceTable/CustomerInvoiceTable.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import FlexpriceTable, { ColumnData } from '../Table';
3 | import { getCurrencySymbol, toSentenceCase } from '@/utils/common/helper_functions';
4 | import { Invoice } from '@/models/Invoice';
5 | import { getPaymentStatusChip } from './InvoiceTable';
6 |
7 | import InvoiceTableMenu from './InvoiceTableMenu';
8 |
9 | interface Props {
10 | data: Invoice[];
11 | customerId?: string;
12 | onRowClick?: (row: Invoice) => void;
13 | }
14 |
15 | const CustomerInvoiceTable: FC = ({ data, onRowClick }) => {
16 | const columnData: ColumnData[] = [
17 | {
18 | title: 'Invoice Number',
19 | render: (row) => <>{row.invoice_number || '--'}>,
20 | },
21 | {
22 | title: 'Status',
23 | render: (row) => <>{toSentenceCase(row.invoice_status)}>,
24 | },
25 | {
26 | title: 'Payment Status',
27 |
28 | render: (row: Invoice) => getPaymentStatusChip(row.payment_status),
29 | },
30 | {
31 | title: 'Total Amount',
32 | render: (row) => <>{`${getCurrencySymbol(row.currency)} ${row.amount_due}`}>,
33 | },
34 | {
35 | fieldVariant: 'interactive',
36 | hideOnEmpty: true,
37 | render: (row) => ,
38 | },
39 | ];
40 |
41 | return (
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default CustomerInvoiceTable;
49 |
--------------------------------------------------------------------------------
/src/api/MeterApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { Pagination } from '@/models';
3 | import { CreateMeterRequest, UpdateMeterRequest, MeterResponse, GetAllMetersResponse, ListMetersResponse } from '@/types/dto';
4 |
5 | export class MeterApi {
6 | private static baseUrl = '/meters';
7 |
8 | public static async createMeter(data: CreateMeterRequest) {
9 | return await AxiosClient.post(this.baseUrl, data);
10 | }
11 |
12 | public static async getAllMeters({ limit, offset }: Pagination) {
13 | return await AxiosClient.get(`${this.baseUrl}?limit=${limit}&offset=${offset}`);
14 | }
15 |
16 | public static async getAllActiveMeters() {
17 | return await AxiosClient.get(`${this.baseUrl}?status=published&limit=1000`);
18 | }
19 |
20 | public static async getMeterById(id: string) {
21 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
22 | }
23 |
24 | public static async updateMeter(id: string, data: UpdateMeterRequest) {
25 | return await AxiosClient.put(`${this.baseUrl}/${id}`, data);
26 | }
27 |
28 | public static async deleteMeter(id: string) {
29 | return await AxiosClient.delete(`${this.baseUrl}/${id}`);
30 | }
31 |
32 | public static async listMeters({ limit, offset }: Pagination) {
33 | return await AxiosClient.get(`${this.baseUrl}?limit=${limit}&offset=${offset}`);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/api/ConnectionApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { Connection, ENTITY_STATUS } from '@/models';
3 | import { generateQueryParams } from '@/utils/common/api_helper';
4 | import { GetConnectionsPayload, GetConnectionsResponse, CreateConnectionPayload, UpdateConnectionPayload } from '@/types/dto';
5 |
6 | class ConnectionApi {
7 | private static baseUrl = '/connections';
8 |
9 | public static async List(payload: GetConnectionsPayload = {}): Promise {
10 | const url = generateQueryParams(this.baseUrl, payload);
11 | return await AxiosClient.get(url);
12 | }
13 |
14 | public static async Get(id: string): Promise {
15 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
16 | }
17 |
18 | public static async ListPublished(): Promise {
19 | return this.List({ status: ENTITY_STATUS.PUBLISHED });
20 | }
21 |
22 | public static async Create(payload: CreateConnectionPayload): Promise {
23 | return await AxiosClient.post(this.baseUrl, payload);
24 | }
25 |
26 | public static async Update(id: string, payload: Partial): Promise {
27 | return await AxiosClient.put(`${this.baseUrl}/${id}`, payload);
28 | }
29 |
30 | public static async Delete(id: string): Promise {
31 | return await AxiosClient.delete(`${this.baseUrl}/${id}`);
32 | }
33 | }
34 |
35 | export default ConnectionApi;
36 |
--------------------------------------------------------------------------------
/src/core/auth/AuthService.ts:
--------------------------------------------------------------------------------
1 | import { NODE_ENV, NodeEnv } from '@/types';
2 | import supabase from '../services/supbase/config';
3 | import { RouteNames } from '../routes/Routes';
4 |
5 | class AuthService {
6 | public static async getAcessToken() {
7 | if (NODE_ENV != NodeEnv.SELF_HOSTED) {
8 | const {
9 | data: { session },
10 | } = await supabase.auth.getSession();
11 | return session?.access_token;
12 | } else {
13 | try {
14 | const tokenData = localStorage.getItem('token');
15 | if (!tokenData) return null;
16 | const parsedToken = JSON.parse(tokenData);
17 | return parsedToken.token;
18 | } catch (error) {
19 | console.error('Error parsing token:', error);
20 | return null;
21 | }
22 | }
23 | }
24 |
25 | public static async getUser() {
26 | if (NODE_ENV != NodeEnv.SELF_HOSTED) {
27 | const { data } = await supabase.auth.getUser();
28 | return data.user;
29 | } else {
30 | try {
31 | const tokenData = localStorage.getItem('token');
32 | if (!tokenData) return null;
33 | const parsedToken = JSON.parse(tokenData);
34 | return parsedToken.user;
35 | } catch (error) {
36 | console.error('Error parsing user data:', error);
37 | return null;
38 | }
39 | }
40 | }
41 |
42 | public static async logout() {
43 | if (NODE_ENV != NodeEnv.SELF_HOSTED) {
44 | await supabase.auth.signOut();
45 | }
46 | localStorage.clear();
47 | window.location.href = RouteNames.login;
48 | }
49 | }
50 |
51 | export default AuthService;
52 |
--------------------------------------------------------------------------------
/src/components/molecules/MetricCard.tsx:
--------------------------------------------------------------------------------
1 | import { formatNumber } from '@/utils/common';
2 | import { getCurrencySymbol } from '@/utils/common/helper_functions';
3 | import { TrendingUp, TrendingDown } from 'lucide-react';
4 |
5 | interface MetricCardProps {
6 | title: string;
7 | value: number;
8 | currency?: string;
9 | isPercent?: boolean;
10 | showChangeIndicator?: boolean;
11 | isNegative?: boolean;
12 | }
13 |
14 | const MetricCard: React.FC = ({
15 | title,
16 | value,
17 | currency,
18 | isPercent = false,
19 | showChangeIndicator = false,
20 | isNegative = false,
21 | }) => {
22 | const arrowColor = isNegative ? 'text-[#DC2626]' : 'text-[#16A34A]';
23 |
24 | const renderValue = () => {
25 | if (isPercent) {
26 | return `${formatNumber(value, 2)}%`;
27 | }
28 | if (currency) {
29 | return `${getCurrencySymbol(currency)} ${formatNumber(value, 2)}`;
30 | }
31 | return formatNumber(value, 2);
32 | };
33 |
34 | return (
35 |
36 |
{title}
37 |
38 | {renderValue()}
39 | {showChangeIndicator && (
40 | {isNegative ? : }
41 | )}
42 |
43 |
44 | );
45 | };
46 |
47 | export default MetricCard;
48 |
--------------------------------------------------------------------------------
/src/types/dto/CreditNote.ts:
--------------------------------------------------------------------------------
1 | import { Pagination, CreditNoteLineItem, CREDIT_NOTE_STATUS, CREDIT_NOTE_REASON, CREDIT_NOTE_TYPE, Metadata, CreditNote } from '@/models';
2 | import { QueryFilter, TimeRangeFilter } from './base';
3 |
4 | // API Payloads
5 | export interface GetAllCreditNotesPayload extends QueryFilter, TimeRangeFilter {
6 | credit_note_ids?: string[];
7 | invoice_id?: string;
8 | credit_note_status?: CREDIT_NOTE_STATUS[];
9 | credit_note_type?: CREDIT_NOTE_TYPE;
10 | }
11 |
12 | export interface CreateCreditNoteParams {
13 | credit_note_number?: string;
14 | invoice_id: string;
15 | memo?: string;
16 | reason: CREDIT_NOTE_REASON;
17 | metadata?: Metadata;
18 | line_items: CreateCreditNoteLineItemRequest[];
19 | idempotency_key?: string;
20 | process_credit_note?: boolean;
21 | }
22 |
23 | export interface CreateCreditNoteLineItemRequest {
24 | invoice_line_item_id: string;
25 | display_name?: string;
26 | amount: number;
27 | metadata?: Metadata;
28 | }
29 |
30 | export interface ProcessDraftCreditNoteParams {
31 | credit_note_id: string;
32 | }
33 |
34 | export interface VoidCreditNoteParams {
35 | credit_note_id: string;
36 | reason?: string;
37 | }
38 |
39 | // API Responses
40 | export interface ListCreditNotesResponse {
41 | items: CreditNote[];
42 | pagination: Pagination;
43 | }
44 |
45 | // Export model types for convenience
46 | export type { CreditNote, CreditNoteLineItem };
47 | export { CREDIT_NOTE_STATUS as CreditNoteStatus, CREDIT_NOTE_REASON as CreditNoteReason, CREDIT_NOTE_TYPE };
48 |
--------------------------------------------------------------------------------
/src/api/SecretKeysApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { Pagination, SecretKey } from '@/models';
3 | import { generateQueryParams } from '@/utils/common/api_helper';
4 | import { GetAllSecretKeysResponse, CreateSecretKeyPayload, CreateSecretKeyResponse } from '@/types/dto';
5 | // Utility function to format permissions for display
6 | export const formatPermissionDisplay = (permissions: string[]): string => {
7 | const hasRead = permissions.includes('read');
8 | const hasWrite = permissions.includes('write');
9 |
10 | if (hasRead && hasWrite) {
11 | return 'full access';
12 | } else if (hasRead) {
13 | return 'read';
14 | } else if (hasWrite) {
15 | return 'write';
16 | } else {
17 | return 'none';
18 | }
19 | };
20 |
21 | class SecretKeysApi {
22 | private static baseUrl = '/secrets/api/keys';
23 |
24 | public static async getAllSecretKeys(pagination: Pagination) {
25 | const url = generateQueryParams(this.baseUrl, pagination);
26 | return await AxiosClient.get(url);
27 | }
28 |
29 | public static async getSecretKeyById(id: string) {
30 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
31 | }
32 |
33 | public static async createSecretKey(data: CreateSecretKeyPayload) {
34 | return await AxiosClient.post(this.baseUrl, data);
35 | }
36 |
37 | public static async deleteSecretKey(id: string) {
38 | return await AxiosClient.delete(`${this.baseUrl}/${id}`);
39 | }
40 | }
41 |
42 | export default SecretKeysApi;
43 |
--------------------------------------------------------------------------------
/src/components/molecules/Tabs/CustomTabs.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
3 |
4 | interface TabItem {
5 | value: string;
6 | label: string;
7 | content: React.ReactNode;
8 | }
9 |
10 | interface CustomTabsProps {
11 | tabs: TabItem[];
12 | defaultValue?: string;
13 | className?: string;
14 | }
15 |
16 | const CustomTabs = ({ tabs, defaultValue = tabs[0]?.value, className }: CustomTabsProps) => {
17 | return (
18 |
19 |
20 | {tabs.map((tab) => (
21 |
31 | {tab.label}
32 |
33 | ))}
34 |
35 |
36 | {tabs.map((tab) => (
37 |
38 | {tab.content}
39 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | export default CustomTabs;
47 |
--------------------------------------------------------------------------------
/src/models/Payment.ts:
--------------------------------------------------------------------------------
1 | import { PAYMENT_STATUS } from '@/constants';
2 | import { BaseModel, Metadata } from './base';
3 |
4 | export interface Payment extends BaseModel {
5 | readonly amount: number;
6 | readonly attempts: Attempt[];
7 | readonly invoice_number: string;
8 | readonly currency: string;
9 | readonly destination_id: string;
10 | readonly destination_type: PAYMENT_DESTINATION_TYPE;
11 | readonly error_message: string;
12 | readonly failed_at: string;
13 | readonly idempotency_key: string;
14 | readonly metadata: Metadata;
15 | readonly payment_method_id: string;
16 | readonly payment_method_type: PAYMENT_METHOD_TYPE;
17 | readonly payment_status: PAYMENT_STATUS;
18 | readonly refunded_at: string;
19 | readonly succeeded_at: string;
20 | readonly track_attempts: boolean;
21 | readonly payment_gateway?: string;
22 | readonly gateway_payment_id?: string;
23 | readonly gateway_tracking_id?: string;
24 | readonly gateway_metadata?: Metadata;
25 | readonly payment_url?: string;
26 | readonly session_id?: string;
27 | }
28 |
29 | export interface Attempt extends BaseModel {
30 | readonly attempt_number: number;
31 | readonly error_message: string;
32 | readonly metadata: Metadata;
33 | readonly payment_id: string;
34 | }
35 |
36 | export enum PAYMENT_METHOD_TYPE {
37 | CARD = 'CARD',
38 | ACH = 'ACH',
39 | OFFLINE = 'OFFLINE',
40 | CREDITS = 'CREDITS',
41 | PAYMENT_LINK = 'PAYMENT_LINK',
42 | }
43 |
44 | export enum PAYMENT_DESTINATION_TYPE {
45 | INVOICE = 'INVOICE',
46 | SUBSCRIPTION = 'SUBSCRIPTION',
47 | }
48 |
--------------------------------------------------------------------------------
/src/pages/customer/payments/PaymentList.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@tanstack/react-query';
2 | import PaymentApi from '@/api/PaymentApi';
3 | import usePagination from '@/hooks/usePagination';
4 | import { Loader, ShortPagination } from '@/components/atoms';
5 | import toast from 'react-hot-toast';
6 | import { InvoicePaymentsTable } from '@/components/molecules';
7 | import { EmptyPage } from '@/components/organisms';
8 | import GUIDES from '@/constants/guides';
9 |
10 | const PaymentList = () => {
11 | const { limit, offset, page } = usePagination();
12 |
13 | const {
14 | data: payments,
15 | isLoading,
16 | isError,
17 | } = useQuery({
18 | queryKey: ['payments', page],
19 | queryFn: () => PaymentApi.getAllPayments({ limit, offset }),
20 | });
21 |
22 | if (isLoading) {
23 | return ;
24 | }
25 |
26 | if (isError) {
27 | toast.error('Error fetching payments');
28 | return null;
29 | }
30 |
31 | if ((payments?.items ?? []).length === 0) {
32 | return (
33 |
42 | );
43 | }
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default PaymentList;
54 |
--------------------------------------------------------------------------------
/src/components/atoms/CodeBlock/CodeBlock.tsx:
--------------------------------------------------------------------------------
1 | import { Copy } from 'lucide-react';
2 | import { Highlight, PrismTheme, themes } from 'prism-react-renderer';
3 | import toast from 'react-hot-toast';
4 | import { FC } from 'react';
5 | import { cn } from '@/lib/utils';
6 |
7 | interface CodeBlockProps {
8 | code: string;
9 | language: string;
10 | theme?: PrismTheme | undefined;
11 | className?: string;
12 | }
13 |
14 | const CodeBlock: FC = ({ code, language, theme = themes.nightOwl, className }) => {
15 | const handleCopyCode = () => {
16 | navigator.clipboard.writeText(code);
17 | toast.success('Code copied to clipboard!');
18 | };
19 |
20 | return (
21 |
22 |
23 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
24 |
25 | {tokens.map((line, i) => (
26 |
27 | {line.map((token, key) => (
28 |
29 | ))}
30 |
31 | ))}
32 |
33 | )}
34 |
35 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default CodeBlock;
46 |
--------------------------------------------------------------------------------
/src/api/index.ts:
--------------------------------------------------------------------------------
1 | // API Exports
2 | export { default as AddonApi } from './AddonApi';
3 | export { default as AuthApi } from './AuthApi';
4 | export { default as ConnectionApi } from './ConnectionApi';
5 | export { default as CostSheetApi } from './CostSheetApi';
6 | export { default as CouponApi } from './CouponApi';
7 | export { default as CreditGrantApi } from './CreditGrantApi';
8 | export { default as CreditNoteApi } from './CreditNoteApi';
9 | export { default as CustomerApi } from './CustomerApi';
10 | export { default as EntitlementApi } from './EntitlementApi';
11 | export { default as EnvironmentApi } from './EnvironmentApi';
12 | export { default as EventsApi } from './EventsApi';
13 | export { default as ExportRunApi } from './ExportRunApi';
14 | export { default as FeatureApi } from './FeatureApi';
15 | export { default as IntegrationsApi } from './IntegrationsApi';
16 | export { default as InvoiceApi } from './InvoiceApi';
17 | export { MeterApi } from './MeterApi';
18 | export { default as PaymentApi } from './PaymentApi';
19 | export { PlanApi } from './PlanApi';
20 | export { PriceApi } from './PriceApi';
21 | export { default as SecretKeysApi } from './SecretKeysApi';
22 | export { default as SubscriptionApi } from './SubscriptionApi';
23 | export { default as TaskApi } from './TaskApi';
24 | export { default as TaskRunApi } from './TaskRunApi';
25 | export { default as TaxApi } from './TaxApi';
26 | export { default as TenantApi } from './TenantApi';
27 | export { UserApi } from './UserApi';
28 | export { default as WalletApi } from './WalletApi';
29 | export { default as WebhookApi } from './WebhookApi';
30 |
--------------------------------------------------------------------------------
/src/components/atoms/Input/Input.stories.tsx:
--------------------------------------------------------------------------------
1 | // import type { Meta, StoryObj } from '@storybook/react';
2 | // import Input from './Input';
3 |
4 | // const meta = {
5 | // title: 'Atoms/Input',
6 | // component: Input,
7 | // parameters: {
8 | // layout: 'centered',
9 | // },
10 | // tags: ['autodocs'],
11 | // } satisfies Meta;
12 |
13 | // export default meta;
14 | // type Story = StoryObj;
15 |
16 | // export const Default: Story = {
17 | // args: {
18 | // placeholder: 'Enter text here',
19 | // },
20 | // };
21 |
22 | // export const WithLabel: Story = {
23 | // args: {
24 | // label: 'Email',
25 | // placeholder: 'Enter your email',
26 | // type: 'email',
27 | // },
28 | // };
29 |
30 | // export const WithError: Story = {
31 | // args: {
32 | // label: 'Password',
33 | // type: 'password',
34 | // error: 'Password must be at least 8 characters',
35 | // placeholder: 'Enter your password',
36 | // },
37 | // };
38 |
39 | // export const Disabled: Story = {
40 | // args: {
41 | // label: 'Username',
42 | // placeholder: 'Enter your username',
43 | // disabled: true,
44 | // },
45 | // };
46 |
47 | // export const FullWidth: Story = {
48 | // args: {
49 | // label: 'Full Name',
50 | // placeholder: 'Enter your full name',
51 | // fullWidth: true,
52 | // },
53 | // parameters: {
54 | // layout: 'padded',
55 | // },
56 | // };
57 |
58 | // export const WithValue: Story = {
59 | // args: {
60 | // label: 'Name',
61 | // value: 'John Doe',
62 | // placeholder: 'Enter your name',
63 | // },
64 | // };
65 |
--------------------------------------------------------------------------------
/src/models/Analytics.ts:
--------------------------------------------------------------------------------
1 | import { Price } from './Price';
2 | import { Meter, METER_AGGREGATION_TYPE } from './Meter';
3 | import { Feature } from './Feature';
4 | import { LineItem as SubscriptionLineItem } from './Subscription';
5 | import { Plan } from './Plan';
6 | import Addon from './Addon';
7 |
8 | // WindowSize enum matching backend types
9 | export enum WindowSize {
10 | MINUTE = 'MINUTE',
11 | FIFTEEN_MIN = '15MIN',
12 | THIRTY_MIN = '30MIN',
13 | HOUR = 'HOUR',
14 | THREE_HOUR = '3HOUR',
15 | SIX_HOUR = '6HOUR',
16 | TWELVE_HOUR = '12HOUR',
17 | DAY = 'DAY',
18 | WEEK = 'WEEK',
19 | MONTH = 'MONTH',
20 | }
21 |
22 | // UsageAnalyticPoint represents a point in the time series data
23 | export interface UsageAnalyticPoint {
24 | timestamp: string;
25 | usage: number;
26 | cost: number;
27 | event_count: number;
28 | }
29 |
30 | // UsageAnalyticItem represents a single analytic item in the response
31 | export interface UsageAnalyticItem {
32 | feature_id: string;
33 | price_id?: string;
34 | meter_id?: string;
35 | sub_line_item_id?: string;
36 | subscription_id?: string;
37 | price?: Price;
38 | meter?: Meter;
39 | feature?: Feature;
40 | subscription_line_item?: SubscriptionLineItem;
41 | plan?: Plan;
42 | addon?: Addon;
43 | name?: string;
44 | event_name?: string;
45 | source?: string;
46 | unit?: string;
47 | unit_plural?: string;
48 | aggregation_type?: METER_AGGREGATION_TYPE;
49 | total_usage: number;
50 | total_cost: number;
51 | currency?: string;
52 | event_count: number;
53 | properties?: Record;
54 | points?: UsageAnalyticPoint[];
55 | add_on_id?: string;
56 | plan_id?: string;
57 | }
58 |
--------------------------------------------------------------------------------
/src/types/dto/Task.ts:
--------------------------------------------------------------------------------
1 | import { ImportTask, Pagination, Metadata, ScheduledTask, ScheduledEntityType, ScheduledTaskInterval } from '@/models';
2 |
3 | export interface AddTaskPayload {
4 | entity_type: string;
5 | file_type: string;
6 | file_url: string;
7 | task_type: string;
8 | file_name?: string;
9 | metadata?: Metadata;
10 | }
11 |
12 | export interface GetTasksPayload {
13 | created_by?: string;
14 | end_time?: string;
15 | expand?: string;
16 | limit?: number;
17 | offset?: number;
18 | order?: string;
19 | sort?: string;
20 | start_time?: string;
21 | status?: string;
22 | task_status?: string;
23 | task_type?: string;
24 | }
25 |
26 | export interface GetTasksResponse {
27 | items: ImportTask[];
28 | pagination: Pagination;
29 | }
30 |
31 | // Scheduled Task DTOs
32 | export interface GetScheduledTasksPayload {
33 | connection_id?: string;
34 | limit?: number;
35 | offset?: number;
36 | }
37 |
38 | export interface GetScheduledTasksResponse {
39 | items: ScheduledTask[];
40 | pagination: Pagination;
41 | }
42 |
43 | export interface CreateScheduledTaskPayload {
44 | connection_id: string;
45 | entity_type: ScheduledEntityType;
46 | interval: ScheduledTaskInterval;
47 | enabled: boolean;
48 | job_config: {
49 | bucket: string;
50 | region: string;
51 | key_prefix: string;
52 | compression?: string;
53 | encryption?: string;
54 | max_file_size_mb?: number;
55 | endpoint_url?: string;
56 | use_path_style?: boolean;
57 | };
58 | }
59 |
60 | export interface UpdateScheduledTaskPayload {
61 | enabled: boolean;
62 | }
63 |
64 | export interface ForceRunPayload {
65 | start_time?: string;
66 | end_time?: string;
67 | }
68 |
--------------------------------------------------------------------------------
/src/api/ExportRunApi.ts:
--------------------------------------------------------------------------------
1 | import { AxiosClient } from '@/core/axios/verbs';
2 | import { generateQueryParams } from '@/utils/common/api_helper';
3 |
4 | export interface ExportRun {
5 | id: string;
6 | scheduled_task_id: string;
7 | status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
8 | started_at?: string;
9 | completed_at?: string;
10 | error_message?: string;
11 | records_processed?: number;
12 | records_exported?: number;
13 | file_size_bytes?: number;
14 | file_path?: string;
15 | created_at: string;
16 | updated_at: string;
17 | }
18 |
19 | export interface GetExportRunsPayload {
20 | scheduled_task_id?: string;
21 | status?: string;
22 | limit?: number;
23 | offset?: number;
24 | }
25 |
26 | export interface GetExportRunsResponse {
27 | items: ExportRun[];
28 | pagination: {
29 | total: number;
30 | limit: number;
31 | offset: number;
32 | };
33 | }
34 |
35 | class ExportRunApi {
36 | private static baseUrl = '/export-runs';
37 |
38 | public static async getAllExportRuns(payload: GetExportRunsPayload = {}): Promise {
39 | const url = generateQueryParams(this.baseUrl, payload);
40 | return await AxiosClient.get(url);
41 | }
42 |
43 | public static async getExportRunById(id: string): Promise {
44 | return await AxiosClient.get(`${this.baseUrl}/${id}`);
45 | }
46 |
47 | public static async getExportRunsByTaskId(
48 | taskId: string,
49 | payload: Omit = {},
50 | ): Promise {
51 | return await this.getAllExportRuns({ ...payload, scheduled_task_id: taskId });
52 | }
53 | }
54 |
55 | export default ExportRunApi;
56 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const ScrollArea = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, children, ...props }, ref) => (
10 |
11 | {children}
12 |
13 |
14 |
15 | ));
16 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
17 |
18 | const ScrollBar = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, orientation = 'vertical', ...props }, ref) => (
22 |
32 |
33 |
34 | ));
35 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
36 |
37 | export { ScrollArea, ScrollBar };
38 |
--------------------------------------------------------------------------------
/src/constants/loading_quotes.ts:
--------------------------------------------------------------------------------
1 | const loadingQuotes = [
2 | // Professional quotes
3 | 'Calculating your usage metrics...',
4 | 'Analyzing your consumption patterns...',
5 | 'Preparing your billing insights...',
6 |
7 | // Fun and engaging quotes
8 | 'Counting bytes and bits, just for you! 🔢',
9 | 'Teaching our calculators to dance... 💃',
10 | 'Making sure every penny finds its home 🏠',
11 | 'Doing the usage-math dance, almost there! 🕺',
12 | 'Turning your usage into beautiful charts ✨',
13 | 'Our hamsters are running extra fast today! 🐹',
14 | 'Brewing your usage insights with extra care ☕',
15 | 'Making numbers look pretty for you 💅',
16 | 'Sprinkling some magic on your metrics ✨',
17 |
18 | // Informative but friendly
19 | 'Your usage story is loading... 📚',
20 | 'Painting your usage picture with data 🎨',
21 | 'Getting your billing insights ready with a smile 😊',
22 | 'Measuring your awesome work in progress 📊',
23 | 'Converting coffee into calculations ☕',
24 | 'Making sure we count every bit of your success 🌟',
25 |
26 | // Motivational
27 | 'Great things take time, almost there! 🚀',
28 | 'Creating insights worth waiting for ⭐',
29 | 'Making your data look its Sunday best 👔',
30 | 'Preparing a feast of insights for you 🍽️',
31 |
32 | // Tech humor
33 | 'Asking AI to count faster... beep boop 🤖',
34 | 'Our servers are doing synchronized swimming 🏊♂️',
35 | 'Quantum computing your bill (just kidding!) 😄',
36 | 'Training our pixels to march in line 👾',
37 |
38 | // Usage-specific wit
39 | 'Turning usage into beautiful stories 📖',
40 | 'Making sure every click counts (literally!) 🖱️',
41 | "Doing complex math so you don't have to 🧮",
42 | ];
43 |
44 | export default loadingQuotes;
45 |
--------------------------------------------------------------------------------