fix(payment): resolve PR audit issues
- Add payment navigation to AppSidebar (user orders + admin payment menu group with collapse) - Add 5 missing nav i18n keys (myOrders, orderManagement, paymentDashboard, paymentConfig, paymentPlans) - Renumber payment migrations 090-100 → 092-102 to avoid conflict with upstream 090/091 - Remove non-payment sora_client_enabled change, restore upstream purchase_subscription fields - Remove extra 'data' from SettingsTab type union
This commit is contained in:
@@ -29,30 +29,64 @@
|
||||
<template v-if="isAdmin">
|
||||
<!-- Admin Section -->
|
||||
<div class="sidebar-section">
|
||||
<router-link
|
||||
v-for="item in adminNavItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="sidebar-link mb-1"
|
||||
:class="{ 'sidebar-link-active': isActive(item.path) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
:id="
|
||||
item.path === '/admin/accounts'
|
||||
? 'sidebar-channel-manage'
|
||||
: item.path === '/admin/groups'
|
||||
? 'sidebar-group-manage'
|
||||
: item.path === '/admin/redeem'
|
||||
? 'sidebar-wallet'
|
||||
: undefined
|
||||
"
|
||||
@click="handleMenuItemClick(item.path)"
|
||||
>
|
||||
<span v-if="item.iconSvg" class="h-5 w-5 flex-shrink-0 sidebar-svg-icon" v-html="sanitizeSvg(item.iconSvg)"></span>
|
||||
<component v-else :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed">{{ item.label }}</span>
|
||||
</transition>
|
||||
</router-link>
|
||||
<template v-for="item in adminNavItems" :key="item.path">
|
||||
<!-- Collapsible group (has children) -->
|
||||
<template v-if="item.children?.length">
|
||||
<button
|
||||
type="button"
|
||||
class="sidebar-link mb-1 w-full"
|
||||
:class="{ 'sidebar-link-active': isGroupActive(item) && !isGroupExpanded(item) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
@click="sidebarCollapsed ? undefined : toggleGroup(item)"
|
||||
>
|
||||
<component :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed" class="flex flex-1 items-center justify-between">
|
||||
<span>{{ item.label }}</span>
|
||||
<ChevronDownIcon class="h-4 w-4 flex-shrink-0 transition-transform duration-200" :class="isGroupExpanded(item) ? 'rotate-180' : ''" />
|
||||
</span>
|
||||
</transition>
|
||||
</button>
|
||||
<!-- Children -->
|
||||
<div v-if="!sidebarCollapsed && isGroupExpanded(item)" class="mb-1 ml-4 border-l border-gray-200 pl-2 dark:border-dark-600">
|
||||
<router-link
|
||||
v-for="child in item.children"
|
||||
:key="child.path"
|
||||
:to="child.path"
|
||||
class="sidebar-link mb-0.5 py-1.5 text-sm"
|
||||
:class="{ 'sidebar-link-active': route.path === child.path }"
|
||||
@click="handleMenuItemClick(child.path)"
|
||||
>
|
||||
<component :is="child.icon" class="h-4 w-4 flex-shrink-0" />
|
||||
<span>{{ child.label }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Normal item (no children) -->
|
||||
<router-link
|
||||
v-else
|
||||
:to="item.path"
|
||||
class="sidebar-link mb-1"
|
||||
:class="{ 'sidebar-link-active': isActive(item.path) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
:id="
|
||||
item.path === '/admin/accounts'
|
||||
? 'sidebar-channel-manage'
|
||||
: item.path === '/admin/groups'
|
||||
? 'sidebar-group-manage'
|
||||
: item.path === '/admin/redeem'
|
||||
? 'sidebar-wallet'
|
||||
: undefined
|
||||
"
|
||||
@click="handleMenuItemClick(item.path)"
|
||||
>
|
||||
<span v-if="item.iconSvg" class="h-5 w-5 flex-shrink-0 sidebar-svg-icon" v-html="sanitizeSvg(item.iconSvg)"></span>
|
||||
<component v-else :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed">{{ item.label }}</span>
|
||||
</transition>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Personal Section for Admin (hidden in simple mode) -->
|
||||
@@ -160,6 +194,7 @@ interface NavItem {
|
||||
icon: unknown
|
||||
iconSvg?: string
|
||||
hideInSimpleMode?: boolean
|
||||
children?: NavItem[]
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -175,6 +210,9 @@ const mobileOpen = computed(() => appStore.mobileOpen)
|
||||
const isAdmin = computed(() => authStore.isAdmin)
|
||||
const isDark = ref(document.documentElement.classList.contains('dark'))
|
||||
|
||||
// Track which parent nav groups are expanded
|
||||
const expandedGroups = ref<Set<string>>(new Set())
|
||||
|
||||
// Site settings from appStore (cached, no flicker)
|
||||
const siteName = computed(() => appStore.siteName)
|
||||
const siteLogo = computed(() => appStore.siteLogo)
|
||||
@@ -467,6 +505,36 @@ const ChevronDoubleLeftIcon = {
|
||||
)
|
||||
}
|
||||
|
||||
const OrderIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15a2.25 2.25 0 012.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const OrderListIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const ChevronDoubleRightIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
@@ -482,6 +550,21 @@ const ChevronDoubleRightIcon = {
|
||||
)
|
||||
}
|
||||
|
||||
const ChevronDownIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'm19.5 8.25-7.5 7.5-7.5-7.5'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// User navigation items (for regular users)
|
||||
const userNavItems = computed((): NavItem[] => {
|
||||
const items: NavItem[] = [
|
||||
@@ -489,14 +572,24 @@ const userNavItems = computed((): NavItem[] => {
|
||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
...(appStore.cachedPublicSettings?.purchase_subscription_enabled
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/purchase',
|
||||
label: t('nav.buySubscription'),
|
||||
icon: RechargeSubscriptionIcon,
|
||||
hideInSimpleMode: true
|
||||
}
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/orders',
|
||||
label: t('nav.myOrders'),
|
||||
icon: OrderListIcon,
|
||||
hideInSimpleMode: true
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
@@ -517,14 +610,24 @@ const personalNavItems = computed((): NavItem[] => {
|
||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
...(appStore.cachedPublicSettings?.purchase_subscription_enabled
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/purchase',
|
||||
label: t('nav.buySubscription'),
|
||||
icon: RechargeSubscriptionIcon,
|
||||
hideInSimpleMode: true
|
||||
}
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/orders',
|
||||
label: t('nav.myOrders'),
|
||||
icon: OrderListIcon,
|
||||
hideInSimpleMode: true
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
@@ -569,6 +672,21 @@ const adminNavItems = computed((): NavItem[] => {
|
||||
{ path: '/admin/proxies', label: t('nav.proxies'), icon: ServerIcon },
|
||||
{ path: '/admin/redeem', label: t('nav.redeemCodes'), icon: TicketIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/promo-codes', label: t('nav.promoCodes'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
...(adminSettingsStore.paymentEnabled
|
||||
? [
|
||||
{
|
||||
path: '/admin/orders',
|
||||
label: t('nav.orderManagement'),
|
||||
icon: OrderIcon,
|
||||
hideInSimpleMode: true,
|
||||
children: [
|
||||
{ path: '/admin/orders/dashboard', label: t('nav.paymentDashboard'), icon: ChartIcon },
|
||||
{ path: '/admin/orders', label: t('nav.orderManagement'), icon: OrderIcon },
|
||||
{ path: '/admin/orders/plans', label: t('nav.paymentPlans'), icon: CreditCardIcon },
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon }
|
||||
]
|
||||
|
||||
@@ -630,6 +748,23 @@ function isActive(path: string): boolean {
|
||||
return route.path === path || route.path.startsWith(path + '/')
|
||||
}
|
||||
|
||||
function isGroupActive(item: NavItem): boolean {
|
||||
if (!item.children) return false
|
||||
return item.children.some(child => route.path === child.path)
|
||||
}
|
||||
|
||||
function isGroupExpanded(item: NavItem): boolean {
|
||||
return expandedGroups.value.has(item.path) || isGroupActive(item)
|
||||
}
|
||||
|
||||
function toggleGroup(item: NavItem) {
|
||||
if (expandedGroups.value.has(item.path)) {
|
||||
expandedGroups.value.delete(item.path)
|
||||
} else {
|
||||
expandedGroups.value.add(item.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user