mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 07:37:09 +08:00
fix(web): remove unsafe select value casts (#36007)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
279b66bc7f
commit
a643b05368
@ -1334,11 +1334,6 @@
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"web/app/components/base/markdown-blocks/form.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/base/markdown-blocks/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 10
|
||||
@ -4435,11 +4430,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/signin/one-more-step.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/signup/layout.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
|
||||
@ -55,10 +55,12 @@ export type ConfigParams = {
|
||||
|
||||
const prefixSettings = 'overview.appInfo.settings'
|
||||
type SelectOption = {
|
||||
value: string
|
||||
value: Language
|
||||
name: string
|
||||
}
|
||||
|
||||
const LANGUAGE_OPTIONS: SelectOption[] = languages.filter(item => item.supported)
|
||||
|
||||
const createInputInfo = (appInfo: ISettingsModalProps['appInfo']) => {
|
||||
const {
|
||||
title,
|
||||
@ -139,8 +141,13 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
const { enableBilling, plan, webappCopyrightEnabled } = useProviderContext()
|
||||
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
|
||||
const isFreePlan = plan.type === 'sandbox'
|
||||
const languageOptions: SelectOption[] = languages.filter(item => item.supported)
|
||||
const selectedLanguage = languageOptions.find(item => item.value === language)
|
||||
const selectedLanguage = LANGUAGE_OPTIONS.find(item => item.value === language)
|
||||
|
||||
const handleLanguageChange = (nextValue: string | null) => {
|
||||
const nextLanguage = LANGUAGE_OPTIONS.find(item => item.value === nextValue)
|
||||
if (nextLanguage)
|
||||
setLanguage(nextLanguage.value)
|
||||
}
|
||||
const handlePlanClick = useCallback(() => {
|
||||
if (isFreePlan)
|
||||
setShowPricingModal()
|
||||
@ -308,17 +315,17 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
<div className={cn('grow py-1 system-sm-semibold text-text-secondary')}>{t(`${prefixSettings}.language`, { ns: 'appOverview' })}</div>
|
||||
<Select
|
||||
value={selectedLanguage?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
setLanguage(nextValue as Language)
|
||||
}}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger size="large" className="w-[200px]">
|
||||
<SelectTrigger
|
||||
aria-label={t(`${prefixSettings}.language`, { ns: 'appOverview' })}
|
||||
size="large"
|
||||
className="w-[200px]"
|
||||
>
|
||||
{selectedLanguage?.name ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{languageOptions.map(item => (
|
||||
{LANGUAGE_OPTIONS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
|
||||
@ -12,6 +12,7 @@ import Label from '../../label'
|
||||
import { useInputTypeOptions } from './hooks'
|
||||
import Option from './option'
|
||||
import Trigger from './trigger'
|
||||
import { InputTypeEnum } from './types'
|
||||
|
||||
type InputTypeSelectFieldProps = {
|
||||
label: string
|
||||
@ -32,6 +33,12 @@ const InputTypeSelectField = ({
|
||||
const inputTypeOptions = useInputTypeOptions(supportFile)
|
||||
const selected = inputTypeOptions.find(option => option.value === field.state.value)
|
||||
|
||||
const handleInputTypeChange = (next: string | null) => {
|
||||
const inputType = InputTypeEnum.safeParse(next)
|
||||
if (inputType.success)
|
||||
field.handleChange(inputType.data)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
@ -43,11 +50,7 @@ const InputTypeSelectField = ({
|
||||
items={inputTypeOptions}
|
||||
value={field.state.value ?? null}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) => {
|
||||
if (next == null)
|
||||
return
|
||||
field.handleChange(next as InputType)
|
||||
}}
|
||||
onValueChange={handleInputTypeChange}
|
||||
>
|
||||
<SelectTrigger id={field.name} className="gap-x-0.5 px-2">
|
||||
<Trigger option={selected} />
|
||||
|
||||
@ -12,28 +12,32 @@ import { formatDateForOutput, toDayjs } from '@/app/components/base/date-and-tim
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
|
||||
enum DATA_FORMAT {
|
||||
TEXT = 'text',
|
||||
JSON = 'json',
|
||||
}
|
||||
enum SUPPORTED_TAGS {
|
||||
LABEL = 'label',
|
||||
INPUT = 'input',
|
||||
TEXTAREA = 'textarea',
|
||||
BUTTON = 'button',
|
||||
}
|
||||
enum SUPPORTED_TYPES {
|
||||
TEXT = 'text',
|
||||
PASSWORD = 'password',
|
||||
EMAIL = 'email',
|
||||
NUMBER = 'number',
|
||||
DATE = 'date',
|
||||
TIME = 'time',
|
||||
DATETIME = 'datetime',
|
||||
CHECKBOX = 'checkbox',
|
||||
SELECT = 'select',
|
||||
HIDDEN = 'hidden',
|
||||
}
|
||||
const DATA_FORMAT = {
|
||||
TEXT: 'text',
|
||||
JSON: 'json',
|
||||
} as const
|
||||
|
||||
const SUPPORTED_TAGS = {
|
||||
LABEL: 'label',
|
||||
INPUT: 'input',
|
||||
TEXTAREA: 'textarea',
|
||||
BUTTON: 'button',
|
||||
} as const
|
||||
|
||||
const SUPPORTED_TYPES = {
|
||||
TEXT: 'text',
|
||||
PASSWORD: 'password',
|
||||
EMAIL: 'email',
|
||||
NUMBER: 'number',
|
||||
DATE: 'date',
|
||||
TIME: 'time',
|
||||
DATETIME: 'datetime',
|
||||
CHECKBOX: 'checkbox',
|
||||
SELECT: 'select',
|
||||
HIDDEN: 'hidden',
|
||||
} as const
|
||||
|
||||
type SupportedType = typeof SUPPORTED_TYPES[keyof typeof SUPPORTED_TYPES]
|
||||
|
||||
const SUPPORTED_TYPES_SET = new Set<string>(Object.values(SUPPORTED_TYPES))
|
||||
|
||||
@ -253,7 +257,7 @@ const MarkdownForm = ({ node }: { node: HastElement }) => {
|
||||
if (!isSafeName(name))
|
||||
return null
|
||||
|
||||
const type = str(child.properties.type) as SUPPORTED_TYPES
|
||||
const type = str(child.properties.type) as SupportedType
|
||||
|
||||
if (type === SUPPORTED_TYPES.DATE || type === SUPPORTED_TYPES.DATETIME) {
|
||||
return (
|
||||
@ -309,7 +313,10 @@ const MarkdownForm = ({ node }: { node: HastElement }) => {
|
||||
<Select
|
||||
key={key}
|
||||
defaultValue={formValues[name] as string | undefined}
|
||||
onValueChange={val => updateValue(name, val as string)}
|
||||
onValueChange={(val) => {
|
||||
if (val != null)
|
||||
updateValue(name, val)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
|
||||
@ -12,7 +12,12 @@ import { useTheme } from 'next-themes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system'
|
||||
const THEMES = ['light', 'dark', 'system'] as const
|
||||
export type Theme = typeof THEMES[number]
|
||||
|
||||
const isTheme = (value: string): value is Theme => {
|
||||
return (THEMES as readonly string[]).includes(value)
|
||||
}
|
||||
|
||||
export default function ThemeSelector() {
|
||||
const { t } = useTranslation()
|
||||
@ -22,6 +27,11 @@ export default function ThemeSelector() {
|
||||
setTheme(newTheme)
|
||||
}
|
||||
|
||||
const handleThemeValueChange = (value: string) => {
|
||||
if (isTheme(value))
|
||||
handleThemeChange(value)
|
||||
}
|
||||
|
||||
const getCurrentIcon = () => {
|
||||
switch (theme) {
|
||||
case 'light': return <span className="i-ri-sun-line h-4 w-4 text-text-tertiary" />
|
||||
@ -43,7 +53,7 @@ export default function ThemeSelector() {
|
||||
{getCurrentIcon()}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={6} popupClassName="w-[144px]">
|
||||
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={value => handleThemeChange(value as Theme)}>
|
||||
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={handleThemeValueChange}>
|
||||
<DropdownMenuRadioItem value="light" closeOnClick>
|
||||
<span className="i-ri-sun-line h-4 w-4 text-text-tertiary" />
|
||||
<span className="grow px-1 system-md-regular">{t('theme.light', { ns: 'common' })}</span>
|
||||
|
||||
@ -186,6 +186,15 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateTypeChange = (value: string | null) => {
|
||||
const option = visibleOptions.find(item => item.value === value)
|
||||
if (!option)
|
||||
return
|
||||
|
||||
setIsMenuOpen(false)
|
||||
void onChooseCreateType(option.value)
|
||||
}
|
||||
|
||||
const onClickCreate = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (subscriptionCount >= MAX_COUNT) {
|
||||
e.stopPropagation()
|
||||
@ -209,12 +218,7 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
|
||||
value={methodType === DEFAULT_METHOD ? null : methodType}
|
||||
open={shouldAllowSelect ? isMenuOpen : false}
|
||||
onOpenChange={setIsMenuOpen}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
return
|
||||
setIsMenuOpen(false)
|
||||
void onChooseCreateType(value as SupportedCreationMethods)
|
||||
}}
|
||||
onValueChange={handleCreateTypeChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
render={<div />}
|
||||
|
||||
@ -9,8 +9,6 @@ import {
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
} from '@langgenius/dify-ui/select'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type FrequencyOption = {
|
||||
@ -27,19 +25,25 @@ const FrequencySelector = ({ frequency, onChange }: FrequencySelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const groupLabel = t('nodes.triggerSchedule.frequency.label', { ns: 'workflow' })
|
||||
|
||||
const frequencies = useMemo<FrequencyOption[]>(() => [
|
||||
const frequencies: FrequencyOption[] = [
|
||||
{ value: 'hourly', name: t('nodes.triggerSchedule.frequency.hourly', { ns: 'workflow' }) },
|
||||
{ value: 'daily', name: t('nodes.triggerSchedule.frequency.daily', { ns: 'workflow' }) },
|
||||
{ value: 'weekly', name: t('nodes.triggerSchedule.frequency.weekly', { ns: 'workflow' }) },
|
||||
{ value: 'monthly', name: t('nodes.triggerSchedule.frequency.monthly', { ns: 'workflow' }) },
|
||||
], [t])
|
||||
]
|
||||
const selectedFrequency = frequencies.find(item => item.value === frequency)
|
||||
|
||||
const handleFrequencyChange = (value: string | null) => {
|
||||
const selected = frequencies.find(item => item.value === value)
|
||||
if (selected)
|
||||
onChange(selected.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={`${frequency}-${groupLabel}`}
|
||||
value={frequency}
|
||||
onValueChange={value => value && onChange(value as ScheduleFrequency)}
|
||||
onValueChange={handleFrequencyChange}
|
||||
>
|
||||
<SelectTrigger className="w-full py-2">
|
||||
{selectedFrequency?.name ?? t('nodes.triggerSchedule.selectFrequency', { ns: 'workflow' })}
|
||||
|
||||
@ -36,7 +36,7 @@ const HTTP_METHODS = [
|
||||
{ name: 'DELETE', value: 'DELETE' },
|
||||
{ name: 'PATCH', value: 'PATCH' },
|
||||
{ name: 'HEAD', value: 'HEAD' },
|
||||
]
|
||||
] satisfies Array<{ name: string, value: HttpMethod }>
|
||||
|
||||
const CONTENT_TYPES = [
|
||||
{ name: 'application/json', value: 'application/json' },
|
||||
@ -46,6 +46,51 @@ const CONTENT_TYPES = [
|
||||
{ name: 'multipart/form-data', value: 'multipart/form-data' },
|
||||
]
|
||||
|
||||
type WebhookMethodSelectorProps = {
|
||||
nodeId: string
|
||||
label: string
|
||||
value: HttpMethod
|
||||
disabled: boolean
|
||||
onChange: (method: HttpMethod) => void
|
||||
}
|
||||
|
||||
const WebhookMethodSelector = ({
|
||||
nodeId,
|
||||
label,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
}: WebhookMethodSelectorProps) => {
|
||||
const selectedMethod = HTTP_METHODS.find(item => item.value === value) ?? null
|
||||
|
||||
const handleMethodChange = (nextValue: string | null) => {
|
||||
const nextMethod = HTTP_METHODS.find(item => item.value === nextValue)
|
||||
if (nextMethod)
|
||||
onChange(nextMethod.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={`${nodeId}-method-${value}`}
|
||||
value={selectedMethod?.value ?? null}
|
||||
disabled={disabled}
|
||||
onValueChange={handleMethodChange}
|
||||
>
|
||||
<SelectTrigger aria-label={label} className="h-8 pr-8 text-sm">
|
||||
{selectedMethod?.name}
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-26 min-w-26">
|
||||
{HTTP_METHODS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
|
||||
id,
|
||||
data,
|
||||
@ -75,7 +120,6 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
|
||||
}
|
||||
}, [readOnly, inputs.webhook_url, generateWebhookUrl])
|
||||
|
||||
const selectedMethod = HTTP_METHODS.find(item => item.value === inputs.method) ?? null
|
||||
const selectedContentType = CONTENT_TYPES.find(item => item.value === inputs.content_type) ?? null
|
||||
|
||||
return (
|
||||
@ -86,24 +130,13 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
|
||||
<div className="space-y-1">
|
||||
<div className="flex gap-1" style={{ height: '32px' }}>
|
||||
<div className="w-26 shrink-0">
|
||||
<Select
|
||||
key={`${id}-method-${inputs.method}`}
|
||||
value={selectedMethod?.value ?? null}
|
||||
<WebhookMethodSelector
|
||||
nodeId={id}
|
||||
label={t(`${i18nPrefix}.method`, { ns: 'workflow' })}
|
||||
value={inputs.method}
|
||||
disabled={readOnly}
|
||||
onValueChange={value => value && handleMethodChange(value as HttpMethod)}
|
||||
>
|
||||
<SelectTrigger className="h-8 pr-8 text-sm">
|
||||
{selectedMethod?.name}
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-26 min-w-26">
|
||||
{HTTP_METHODS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
onChange={handleMethodChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1" style={{ width: '284px' }}>
|
||||
<InputWithCopy
|
||||
|
||||
@ -21,11 +21,28 @@ import { useInvitationCheck } from '@/service/use-common'
|
||||
import { timezones } from '@/utils/timezone'
|
||||
import { resolvePostLoginRedirect } from '../utils/post-login-redirect'
|
||||
|
||||
type SelectOption = {
|
||||
type LanguageSelectOption = {
|
||||
value: Locale
|
||||
name: string
|
||||
}
|
||||
|
||||
type TimezoneSelectOption = {
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
const LANGUAGE_OPTIONS: LanguageSelectOption[] = languages
|
||||
.filter(item => item.supported)
|
||||
.map(item => ({
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
}))
|
||||
|
||||
const TIMEZONE_OPTIONS: TimezoneSelectOption[] = timezones.map(item => ({
|
||||
value: String(item.value),
|
||||
name: item.name,
|
||||
}))
|
||||
|
||||
export default function InviteSettingsPage() {
|
||||
const { t } = useTranslation()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
@ -35,9 +52,20 @@ export default function InviteSettingsPage() {
|
||||
const [name, setName] = useState('')
|
||||
const [language, setLanguage] = useState(LanguagesSupported[0])
|
||||
const [timezone, setTimezone] = useState(() => Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles')
|
||||
const languageOptions: SelectOption[] = languages.filter(item => item.supported)
|
||||
const selectedLanguage = languageOptions.find(item => item.value === language)
|
||||
const selectedTimezone = timezones.find(item => item.value === timezone)
|
||||
const selectedLanguage = LANGUAGE_OPTIONS.find(item => item.value === language)
|
||||
const selectedTimezone = TIMEZONE_OPTIONS.find(item => item.value === timezone)
|
||||
|
||||
const handleLanguageChange = (nextValue: string | null) => {
|
||||
const nextLanguage = LANGUAGE_OPTIONS.find(item => item.value === nextValue)
|
||||
if (nextLanguage)
|
||||
setLanguage(nextLanguage.value)
|
||||
}
|
||||
|
||||
const handleTimezoneChange = (nextValue: string | null) => {
|
||||
const nextTimezone = TIMEZONE_OPTIONS.find(item => item.value === nextValue)
|
||||
if (nextTimezone)
|
||||
setTimezone(nextTimezone.value)
|
||||
}
|
||||
|
||||
const checkParams = {
|
||||
url: '/activate/check',
|
||||
@ -123,23 +151,19 @@ export default function InviteSettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<label htmlFor="name" className="my-2 system-md-semibold text-text-secondary">
|
||||
<label htmlFor="interface_language" className="my-2 system-md-semibold text-text-secondary">
|
||||
{t('interfaceLanguage', { ns: 'login' })}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<Select
|
||||
value={selectedLanguage?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
setLanguage(nextValue as Locale)
|
||||
}}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger size="large">
|
||||
<SelectTrigger id="interface_language" size="large">
|
||||
{selectedLanguage?.name ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{languageOptions.map(item => (
|
||||
{LANGUAGE_OPTIONS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
@ -156,19 +180,15 @@ export default function InviteSettingsPage() {
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<Select
|
||||
value={selectedTimezone ? String(selectedTimezone.value) : null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
setTimezone(nextValue as string)
|
||||
}}
|
||||
value={selectedTimezone?.value ?? null}
|
||||
onValueChange={handleTimezoneChange}
|
||||
>
|
||||
<SelectTrigger size="large">
|
||||
<SelectTrigger id="timezone" size="large">
|
||||
{selectedTimezone?.name ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{timezones.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
{TIMEZONE_OPTIONS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import type { Reducer } from 'react'
|
||||
import type { LanguagesSupported } from '@/i18n-config/language'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
@ -51,6 +50,19 @@ type SelectOption = {
|
||||
name: string
|
||||
}
|
||||
|
||||
const LANGUAGE_OPTIONS: SelectOption[] = languages.filter(item => item.supported)
|
||||
const TIMEZONE_OPTIONS: SelectOption[] = timezones.map(item => ({
|
||||
value: String(item.value),
|
||||
name: item.name,
|
||||
}))
|
||||
|
||||
const hasStatus = (error: unknown): error is { status: number } => {
|
||||
return typeof error === 'object'
|
||||
&& error !== null
|
||||
&& 'status' in error
|
||||
&& typeof error.status === 'number'
|
||||
}
|
||||
|
||||
const OneMoreStep = () => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
@ -62,9 +74,20 @@ const OneMoreStep = () => {
|
||||
timezone: 'Asia/Shanghai',
|
||||
})
|
||||
const { mutateAsync: submitOneMoreStep, isPending } = useOneMoreStep()
|
||||
const languageOptions: SelectOption[] = languages.filter(item => item.supported)
|
||||
const selectedLanguage = languageOptions.find(item => item.value === state.interface_language)
|
||||
const selectedTimezone = timezones.find(item => item.value === state.timezone)
|
||||
const selectedLanguage = LANGUAGE_OPTIONS.find(item => item.value === state.interface_language)
|
||||
const selectedTimezone = TIMEZONE_OPTIONS.find(item => item.value === state.timezone)
|
||||
|
||||
const handleLanguageChange = (nextValue: string | null) => {
|
||||
const nextLanguage = LANGUAGE_OPTIONS.find(item => item.value === nextValue)
|
||||
if (nextLanguage)
|
||||
dispatch({ type: 'interface_language', value: nextLanguage.value })
|
||||
}
|
||||
|
||||
const handleTimezoneChange = (nextValue: string | null) => {
|
||||
const nextTimezone = TIMEZONE_OPTIONS.find(item => item.value === nextValue)
|
||||
if (nextTimezone)
|
||||
dispatch({ type: 'timezone', value: nextTimezone.value })
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isPending)
|
||||
@ -77,8 +100,8 @@ const OneMoreStep = () => {
|
||||
})
|
||||
router.push('/apps')
|
||||
}
|
||||
catch (error: any) {
|
||||
if (error && error.status === 400)
|
||||
catch (error: unknown) {
|
||||
if (hasStatus(error) && error.status === 400)
|
||||
toast.error(t('invalidInvitationCode', { ns: 'login' }))
|
||||
dispatch({ type: 'failed', payload: null })
|
||||
}
|
||||
@ -136,23 +159,19 @@ const OneMoreStep = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<label htmlFor="name" className="my-2 system-md-semibold text-text-secondary">
|
||||
<label htmlFor="interface_language" className="my-2 system-md-semibold text-text-secondary">
|
||||
{t('interfaceLanguage', { ns: 'login' })}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<Select
|
||||
value={selectedLanguage?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
dispatch({ type: 'interface_language', value: nextValue as typeof LanguagesSupported[number] })
|
||||
}}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger size="large">
|
||||
<SelectTrigger id="interface_language" size="large">
|
||||
{selectedLanguage?.name ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{languageOptions.map(item => (
|
||||
{LANGUAGE_OPTIONS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
@ -168,19 +187,15 @@ const OneMoreStep = () => {
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<Select
|
||||
value={selectedTimezone ? String(selectedTimezone.value) : null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
dispatch({ type: 'timezone', value: nextValue as typeof state.timezone })
|
||||
}}
|
||||
value={selectedTimezone?.value ?? null}
|
||||
onValueChange={handleTimezoneChange}
|
||||
>
|
||||
<SelectTrigger size="large">
|
||||
<SelectTrigger id="timezone" size="large">
|
||||
{selectedTimezone?.name ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{timezones.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
{TIMEZONE_OPTIONS.map(item => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user