feat: use api to show notification

This commit is contained in:
Joel
2026-03-09 14:59:33 +08:00
parent f51a91eb70
commit bc0f01228c
4 changed files with 128 additions and 0 deletions

View File

@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import * as React from 'react'
import { AppInitializer } from '@/app/components/app-initializer'
import InSiteMessageNotification from '@/app/components/app/in-site-message-notification'
import AmplitudeProvider from '@/app/components/base/amplitude'
import GA, { GaType } from '@/app/components/base/ga'
import Zendesk from '@/app/components/base/zendesk'
@ -32,6 +33,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
<RoleRouteGuard>
{children}
</RoleRouteGuard>
<InSiteMessageNotification />
<PartnerStack />
<ReadmePanel />
<GotoAnything />

View File

@ -0,0 +1,100 @@
'use client'
import type { InSiteMessageActionItem } from './in-site-message'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { IS_CLOUD_EDITION } from '@/config'
import { consoleClient, consoleQuery } from '@/service/client'
import InSiteMessage from './in-site-message'
type NotificationBodyPayload = {
actions: InSiteMessageActionItem[]
main: string
}
function isValidActionItem(value: unknown): value is InSiteMessageActionItem {
if (!value || typeof value !== 'object')
return false
const candidate = value as {
action?: unknown
data?: unknown
text?: unknown
type?: unknown
}
return (
typeof candidate.text === 'string'
&& (candidate.type === 'primary' || candidate.type === 'default')
&& (candidate.action === 'link' || candidate.action === 'close')
&& (candidate.data === undefined || typeof candidate.data !== 'function')
)
}
function parseNotificationBody(body: string): NotificationBodyPayload | null {
try {
const parsed = JSON.parse(body) as {
actions?: unknown
main?: unknown
}
if (!parsed || typeof parsed !== 'object')
return null
if (typeof parsed.main !== 'string')
return null
const actions = Array.isArray(parsed.actions)
? parsed.actions.filter(isValidActionItem)
: []
return {
main: parsed.main,
actions,
}
}
catch {
return null
}
}
function InSiteMessageNotification() {
const { t } = useTranslation()
const { data } = useQuery({
queryKey: consoleQuery.notification.queryKey(),
queryFn: async () => {
return await consoleClient.notification()
},
enabled: IS_CLOUD_EDITION,
})
const notification = data?.notifications?.[0]
const parsedBody = notification ? parseNotificationBody(notification.body) : null
if (!IS_CLOUD_EDITION || !notification)
return null
const fallbackActions: InSiteMessageActionItem[] = [
{
type: 'default',
text: t('operation.close', { ns: 'common' }),
action: 'close',
},
]
const actions = parsedBody?.actions?.length ? parsedBody.actions : fallbackActions
const main = parsedBody?.main ?? 'Invalid notification body'
return (
<InSiteMessage
key={notification.notification_id}
title={notification.title}
subtitle={notification.subtitle}
headerBgUrl={notification.title_pic_url}
main={main}
actions={actions}
/>
)
}
export default InSiteMessageNotification

View File

@ -0,0 +1,24 @@
import { type } from '@orpc/contract'
import { base } from '../base'
export type ConsoleNotification = {
body: string
frequency: 'once' | 'always'
lang: string
notification_id: string
subtitle: string
title: string
title_pic_url?: string
}
export type ConsoleNotificationResponse = {
notifications: ConsoleNotification[]
should_show: boolean
}
export const notificationContract = base
.route({
path: '/notification',
method: 'GET',
})
.output(type<ConsoleNotificationResponse>())

View File

@ -12,6 +12,7 @@ import {
exploreInstalledAppsContract,
exploreInstalledAppUninstallContract,
} from './console/explore'
import { notificationContract } from './console/notification'
import { systemFeaturesContract } from './console/system'
import {
triggerOAuthConfigContract,
@ -67,6 +68,7 @@ export const consoleRouterContract = {
invoices: invoicesContract,
bindPartnerStack: bindPartnerStackContract,
},
notification: notificationContract,
triggers: {
list: triggersContract,
providerInfo: triggerProviderInfoContract,