feat: require compliance confirmation for paid features

Gate payment, redemption, subscription, and invitation reward flows behind an audited compliance acknowledgement.
This commit is contained in:
CaIon
2026-05-13 22:18:46 +08:00
parent aa56667b8f
commit 0526a22643
57 changed files with 1806 additions and 216 deletions
@@ -35,7 +35,7 @@ interface DataTableRowActionsProps {
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
const { t } = useTranslation()
const { setOpen, setCurrentRow } = useSubscriptions()
const { setOpen, setCurrentRow, complianceConfirmed } = useSubscriptions()
return (
<DropdownMenu>
@@ -46,6 +46,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem
disabled={!complianceConfirmed}
onClick={() => {
setCurrentRow(row.original)
setOpen('update')
@@ -55,6 +56,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
{t('Edit')}
</DropdownMenuItem>
<DropdownMenuItem
disabled={!complianceConfirmed}
onClick={() => {
setCurrentRow(row.original)
setOpen('toggle-status')
@@ -23,10 +23,14 @@ import { useSubscriptions } from './subscriptions-provider'
export function SubscriptionsPrimaryButtons() {
const { t } = useTranslation()
const { setOpen } = useSubscriptions()
const { setOpen, complianceConfirmed } = useSubscriptions()
return (
<div className='flex gap-2'>
<Button size='sm' onClick={() => setOpen('create')}>
<Button
size='sm'
onClick={() => setOpen('create')}
disabled={!complianceConfirmed}
>
<Plus className='h-4 w-4' />
{t('Create Plan')}
</Button>
@@ -18,8 +18,14 @@ For commercial licensing, please contact support@quantumnous.com
*/
import React, { useState } from 'react'
import useDialogState from '@/hooks/use-dialog'
import {
getOptionValue,
useSystemOptions,
} from '@/features/system-settings/hooks/use-system-options'
import { type PlanRecord, type SubscriptionsDialogType } from '../types'
const CURRENT_COMPLIANCE_TERMS_VERSION = 'v1'
type SubscriptionsContextType = {
open: SubscriptionsDialogType | null
setOpen: (str: SubscriptionsDialogType | null) => void
@@ -27,6 +33,7 @@ type SubscriptionsContextType = {
setCurrentRow: React.Dispatch<React.SetStateAction<PlanRecord | null>>
refreshTrigger: number
triggerRefresh: () => void
complianceConfirmed: boolean
}
const SubscriptionsContext =
@@ -40,6 +47,15 @@ export function SubscriptionsProvider({
const [open, setOpen] = useDialogState<SubscriptionsDialogType>(null)
const [currentRow, setCurrentRow] = useState<PlanRecord | null>(null)
const [refreshTrigger, setRefreshTrigger] = useState(0)
const { data } = useSystemOptions()
const complianceOptions = getOptionValue(data?.data, {
'payment_setting.compliance_confirmed': false,
'payment_setting.compliance_terms_version': '',
})
const complianceConfirmed =
complianceOptions['payment_setting.compliance_confirmed'] &&
complianceOptions['payment_setting.compliance_terms_version'] ===
CURRENT_COMPLIANCE_TERMS_VERSION
const triggerRefresh = () => setRefreshTrigger((prev) => prev + 1)
@@ -52,6 +68,7 @@ export function SubscriptionsProvider({
setCurrentRow,
refreshTrigger,
triggerRefresh,
complianceConfirmed,
}}
>
{children}
+25 -3
View File
@@ -22,13 +22,18 @@ import { Alert, AlertDescription } from '@/components/ui/alert'
import { SectionPageLayout } from '@/components/layout'
import { SubscriptionsDialogs } from './components/subscriptions-dialogs'
import { SubscriptionsPrimaryButtons } from './components/subscriptions-primary-buttons'
import { SubscriptionsProvider } from './components/subscriptions-provider'
import {
SubscriptionsProvider,
useSubscriptions,
} from './components/subscriptions-provider'
import { SubscriptionsTable } from './components/subscriptions-table'
export function Subscriptions() {
function SubscriptionsContent() {
const { t } = useTranslation()
const { complianceConfirmed } = useSubscriptions()
return (
<SubscriptionsProvider>
<>
<SectionPageLayout>
<SectionPageLayout.Title>
{t('Subscription Management')}
@@ -50,11 +55,28 @@ export function Subscriptions() {
</div>
</SectionPageLayout.Actions>
<SectionPageLayout.Content>
{!complianceConfirmed ? (
<Alert variant='destructive' className='mb-4'>
<AlertDescription>
{t(
'Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.'
)}
</AlertDescription>
</Alert>
) : null}
<SubscriptionsTable />
</SectionPageLayout.Content>
</SectionPageLayout>
<SubscriptionsDialogs />
</>
)
}
export function Subscriptions() {
return (
<SubscriptionsProvider>
<SubscriptionsContent />
</SubscriptionsProvider>
)
}