chore: add support email env (#33075)

This commit is contained in:
Nite Knite 2026-03-06 14:29:29 +08:00 committed by GitHub
parent dc31b07533
commit 0490756ab2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 45 additions and 8 deletions

View File

@ -70,6 +70,7 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
mockConfig: {
IS_CLOUD_EDITION: false,
ZENDESK_WIDGET_KEY: '',
SUPPORT_EMAIL_ADDRESS: '',
},
mockEnv: {
env: {
@ -80,6 +81,7 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
vi.mock('@/config', () => ({
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
get SUPPORT_EMAIL_ADDRESS() { return mockConfig.SUPPORT_EMAIL_ADDRESS },
IS_DEV: false,
IS_CE_EDITION: false,
}))

View File

@ -11,6 +11,10 @@ const { mockZendeskKey } = vi.hoisted(() => ({
mockZendeskKey: { value: 'test-key' },
}))
const { mockSupportEmailKey } = vi.hoisted(() => ({
mockSupportEmailKey: { value: '' },
}))
vi.mock('@/context/app-context', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/context/app-context')>()
return {
@ -33,6 +37,7 @@ vi.mock('@/config', async (importOriginal) => {
...actual,
IS_CE_EDITION: false,
get ZENDESK_WIDGET_KEY() { return mockZendeskKey.value },
get SUPPORT_EMAIL_ADDRESS() { return mockSupportEmailKey.value },
}
})
@ -84,6 +89,7 @@ describe('Support', () => {
vi.clearAllMocks()
window.zE = vi.fn()
mockZendeskKey.value = 'test-key'
mockSupportEmailKey.value = ''
vi.mocked(useAppContext).mockReturnValue(baseAppContextValue)
vi.mocked(useProviderContext).mockReturnValue({
...baseProviderContextValue,
@ -96,7 +102,7 @@ describe('Support', () => {
const renderSupport = () => {
return render(
<DropdownMenu open={true} onOpenChange={() => {}}>
<DropdownMenu open={true} onOpenChange={() => { }}>
<DropdownMenuTrigger>open</DropdownMenuTrigger>
<DropdownMenuContent>
<Support closeAccountDropdown={mockCloseAccountDropdown} />
@ -125,7 +131,7 @@ describe('Support', () => {
})
})
describe('Plan-based Channels', () => {
describe('Dedicated Channels', () => {
it('should show "Contact Us" when ZENDESK_WIDGET_KEY is present', () => {
// Act
renderSupport()
@ -166,6 +172,27 @@ describe('Support', () => {
expect(screen.getByText('common.userProfile.emailSupport')).toBeInTheDocument()
expect(screen.queryByText('common.userProfile.contactUs')).not.toBeInTheDocument()
})
it('should show email support if specified in the config', () => {
// Arrange
mockZendeskKey.value = ''
mockSupportEmailKey.value = 'support@example.com'
vi.mocked(useProviderContext).mockReturnValue({
...baseProviderContextValue,
plan: {
...baseProviderContextValue.plan,
type: Plan.sandbox,
},
})
// Act
renderSupport()
fireEvent.click(screen.getByText('common.userProfile.support'))
// Assert
expect(screen.queryByText('common.userProfile.emailSupport')).toBeInTheDocument()
expect(screen.getByText('common.userProfile.emailSupport')?.closest('a')?.getAttribute('href')).toMatch(new RegExp(`^mailto:${mockSupportEmailKey.value}`))
})
})
describe('Interactions and Links', () => {

View File

@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu'
import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils'
import { Plan } from '@/app/components/billing/type'
import { ZENDESK_WIDGET_KEY } from '@/config'
import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { mailToSupport } from '../utils/util'
@ -17,8 +17,8 @@ export default function Support({ closeAccountDropdown }: SupportProps) {
const { t } = useTranslation()
const { plan } = useProviderContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const hasDedicatedChannel = plan.type !== Plan.sandbox
const hasZendeskWidget = !!ZENDESK_WIDGET_KEY?.trim()
const hasDedicatedChannel = plan.type !== Plan.sandbox || Boolean(SUPPORT_EMAIL_ADDRESS.trim())
const hasZendeskWidget = Boolean(ZENDESK_WIDGET_KEY.trim())
return (
<DropdownMenuSub>
@ -49,7 +49,7 @@ export default function Support({ closeAccountDropdown }: SupportProps) {
{hasDedicatedChannel && !hasZendeskWidget && (
<DropdownMenuItem
className="justify-between"
render={<a href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo?.current_version)} rel="noopener noreferrer" target="_blank" />}
render={<a href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo?.current_version, SUPPORT_EMAIL_ADDRESS)} rel="noopener noreferrer" target="_blank" />}
>
<MenuItemContent
iconClassName="i-ri-mail-send-line"

View File

@ -10,7 +10,7 @@ export const generateMailToLink = (email: string, subject?: string, body?: strin
return mailtoLink
}
export const mailToSupport = (account: string, plan: string, version: string) => {
export const mailToSupport = (account: string, plan: string, version: string, supportEmailAddress?: string) => {
const subject = `Technical Support Request ${plan} ${account}`
const body = `
Please do not remove the following information:
@ -21,5 +21,5 @@ export const mailToSupport = (account: string, plan: string, version: string) =>
Platform:
Problem Description:
`
return generateMailToLink('support@dify.ai', subject, body)
return generateMailToLink(supportEmailAddress || 'support@dify.ai', subject, body)
}

View File

@ -342,6 +342,12 @@ export const ZENDESK_FIELD_IDS = {
'',
),
}
export const SUPPORT_EMAIL_ADDRESS = getStringConfig(
env.NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS,
'',
)
export const APP_VERSION = pkg.version
export const IS_MARKETPLACE = env.NEXT_PUBLIC_IS_MARKETPLACE

View File

@ -115,6 +115,7 @@ const clientSchema = {
*/
NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),
NEXT_PUBLIC_SITE_ABOUT: z.string().optional(),
NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS: z.email().optional(),
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: coercedBoolean.default(false),
/**
* The timeout for the text generation in millisecond
@ -184,6 +185,7 @@ export const env = createEnv({
NEXT_PUBLIC_PUBLIC_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX : getRuntimeEnvFromBody('publicApiPrefix'),
NEXT_PUBLIC_SENTRY_DSN: isServer ? process.env.NEXT_PUBLIC_SENTRY_DSN : getRuntimeEnvFromBody('sentryDsn'),
NEXT_PUBLIC_SITE_ABOUT: isServer ? process.env.NEXT_PUBLIC_SITE_ABOUT : getRuntimeEnvFromBody('siteAbout'),
NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS: isServer ? process.env.NEXT_PUBLIC_SUPPORT_EMAIL_ADDRESS : getRuntimeEnvFromBody('supportEmailAddress'),
NEXT_PUBLIC_SUPPORT_MAIL_LOGIN: isServer ? process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN : getRuntimeEnvFromBody('supportMailLogin'),
NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS: isServer ? process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS : getRuntimeEnvFromBody('textGenerationTimeoutMs'),
NEXT_PUBLIC_TOP_K_MAX_VALUE: isServer ? process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE : getRuntimeEnvFromBody('topKMaxValue'),