diff --git a/web/app/components/base/button/index.css b/web/app/components/base/button/index.css
index 47e59142cc..5899c027d3 100644
--- a/web/app/components/base/button/index.css
+++ b/web/app/components/base/button/index.css
@@ -60,6 +60,7 @@
@apply
border-[0.5px]
shadow-xs
+ backdrop-blur-[5px]
bg-components-button-secondary-bg
border-components-button-secondary-border
hover:bg-components-button-secondary-bg-hover
@@ -69,6 +70,7 @@
.btn-secondary.btn-disabled {
@apply
+ backdrop-blur-sm
bg-components-button-secondary-bg-disabled
border-components-button-secondary-border-disabled
text-components-button-secondary-text-disabled;
diff --git a/web/app/components/billing/pricing/header.tsx b/web/app/components/billing/pricing/header.tsx
new file mode 100644
index 0000000000..60f6d3d248
--- /dev/null
+++ b/web/app/components/billing/pricing/header.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import DifyLogo from '../../base/logo/dify-logo'
+import { useTranslation } from 'react-i18next'
+import Button from '../../base/button'
+import { RiCloseLine } from '@remixicon/react'
+
+type HeaderProps = {
+ onClose: () => void
+}
+
+const Header = ({
+ onClose,
+}: HeaderProps) => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+ {t('billing.plansCommon.title.plans')}
+
+
+
+ {t('billing.plansCommon.title.description')}
+
+
+
+
+ )
+}
+
+export default React.memo(Header)
diff --git a/web/app/components/billing/pricing/index.tsx b/web/app/components/billing/pricing/index.tsx
index 37d85023af..28a43482e9 100644
--- a/web/app/components/billing/pricing/index.tsx
+++ b/web/app/components/billing/pricing/index.tsx
@@ -1,51 +1,63 @@
'use client'
import type { FC } from 'react'
-import React from 'react'
+import React, { useState } from 'react'
import { createPortal } from 'react-dom'
-import { useTranslation } from 'react-i18next'
-import { RiArrowRightUpLine, RiCloseLine, RiCloudFill, RiTerminalBoxFill } from '@remixicon/react'
-import Link from 'next/link'
+import Header from './header'
+import PlanSwitcher from './plan-switcher'
+import { PlanRange } from './plan-switcher/plan-range-switcher'
import { useKeyPress } from 'ahooks'
-import { Plan, SelfHostedPlan } from '../type'
-import TabSlider from '../../base/tab-slider'
-import SelectPlanRange, { PlanRange } from './select-plan-range'
-import PlanItem from './plan-item'
-import SelfHostedPlanItem from './self-hosted-plan-item'
-import { useProviderContext } from '@/context/provider-context'
-import GridMask from '@/app/components/base/grid-mask'
-import { useAppContext } from '@/context/app-context'
-import classNames from '@/utils/classnames'
-import { useGetPricingPageLanguage } from '@/context/i18n'
+// import { useTranslation } from 'react-i18next'
+// import { RiArrowRightUpLine, RiCloseLine, RiCloudFill, RiTerminalBoxFill } from '@remixicon/react'
+// import Link from 'next/link'
+// import { Plan, SelfHostedPlan } from '../type'
+// import TabSlider from '../../base/tab-slider'
+// import PlanItem from './plan-item'
+// import SelfHostedPlanItem from './self-hosted-plan-item'
+// import { useProviderContext } from '@/context/provider-context'
+// import GridMask from '@/app/components/base/grid-mask'
+// import { useAppContext } from '@/context/app-context'
+// import classNames from '@/utils/classnames'
+// import { useGetPricingPageLanguage } from '@/context/i18n'
-type Props = {
+export type Category = 'cloud' | 'self'
+
+type PricingProps = {
onCancel: () => void
}
-const Pricing: FC = ({
+const Pricing: FC = ({
onCancel,
}) => {
- const { t } = useTranslation()
- const { plan } = useProviderContext()
- const { isCurrentWorkspaceManager } = useAppContext()
- const canPay = isCurrentWorkspaceManager
+ // const { t } = useTranslation()
+ // const { plan } = useProviderContext()
+ // const { isCurrentWorkspaceManager } = useAppContext()
+ // const canPay = isCurrentWorkspaceManager
const [planRange, setPlanRange] = React.useState(PlanRange.monthly)
- const [currentPlan, setCurrentPlan] = React.useState('cloud')
+ const [currentCategory, setCurrentCategory] = useState('cloud')
useKeyPress(['esc'], onCancel)
- const pricingPageLanguage = useGetPricingPageLanguage()
- const pricingPageURL = pricingPageLanguage
- ? `https://dify.ai/${pricingPageLanguage}/pricing#plans-and-features`
- : 'https://dify.ai/pricing#plans-and-features'
+ // const pricingPageLanguage = useGetPricingPageLanguage()
+ // const pricingPageURL = pricingPageLanguage
+ // ? `https://dify.ai/${pricingPageLanguage}/pricing#plans-and-features`
+ // : 'https://dify.ai/pricing#plans-and-features'
return createPortal(
e.stopPropagation()}
>
-
-
+
+
+
+ {/*
@@ -137,8 +149,8 @@ const Pricing: FC
= ({
-
-
+ */}
+
,
document.body,
)
diff --git a/web/app/components/billing/pricing/plan-switcher/index.tsx b/web/app/components/billing/pricing/plan-switcher/index.tsx
new file mode 100644
index 0000000000..8fa9fe9a9f
--- /dev/null
+++ b/web/app/components/billing/pricing/plan-switcher/index.tsx
@@ -0,0 +1,64 @@
+import type { FC } from 'react'
+import React from 'react'
+import type { Category } from '../index'
+import { useTranslation } from 'react-i18next'
+import { Ri24HoursFill, RiTerminalBoxFill } from '@remixicon/react'
+import Tab from './tab'
+import Divider from '@/app/components/base/divider'
+import type { PlanRange } from './plan-range-switcher'
+import PlanRangeSwitcher from './plan-range-switcher'
+
+type PlanSwitcherProps = {
+ currentCategory: Category
+ currentPlanRange: PlanRange
+ onChangeCategory: (category: Category) => void
+ onChangePlanRange: (value: PlanRange) => void
+}
+
+const PlanSwitcher: FC = ({
+ currentCategory,
+ currentPlanRange,
+ onChangeCategory,
+ onChangePlanRange,
+}) => {
+ const { t } = useTranslation()
+
+ const tabs = {
+ cloud: {
+ value: 'cloud' as Category,
+ label: t('billing.plansCommon.cloud'),
+ Icon: Ri24HoursFill,
+ },
+ self: {
+ value: 'self' as Category,
+ label: t('billing.plansCommon.self'),
+ Icon: RiTerminalBoxFill,
+ },
+ }
+
+ return (
+
+
+
+
+ {...tabs.cloud}
+ isActive={currentCategory === tabs.cloud.value}
+ onClick={onChangeCategory}
+ />
+
+
+ {...tabs.self}
+ isActive={currentCategory === tabs.self.value}
+ onClick={onChangeCategory}
+ />
+
+
+
+
+ )
+}
+
+export default React.memo(PlanSwitcher)
diff --git a/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx
new file mode 100644
index 0000000000..75276429fb
--- /dev/null
+++ b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx
@@ -0,0 +1,38 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import Switch from '../../../base/switch'
+
+export enum PlanRange {
+ monthly = 'monthly',
+ yearly = 'yearly',
+}
+
+type PlanRangeSwitcherProps = {
+ value: PlanRange
+ onChange: (value: PlanRange) => void
+}
+
+const PlanRangeSwitcher: FC = ({
+ value,
+ onChange,
+}) => {
+ const { t } = useTranslation()
+
+ return (
+
+ {
+ onChange(v ? PlanRange.yearly : PlanRange.monthly)
+ }}
+ />
+
+ {t('billing.plansCommon.annualBilling', { percent: 17 })}
+
+
+ )
+}
+export default React.memo(PlanRangeSwitcher)
diff --git a/web/app/components/billing/pricing/plan-switcher/tab.tsx b/web/app/components/billing/pricing/plan-switcher/tab.tsx
new file mode 100644
index 0000000000..c961b2da0b
--- /dev/null
+++ b/web/app/components/billing/pricing/plan-switcher/tab.tsx
@@ -0,0 +1,37 @@
+import React, { useCallback } from 'react'
+import cn from '@/utils/classnames'
+
+type TabProps = {
+ Icon: React.ComponentType
+ value: T
+ label: string
+ isActive: boolean
+ onClick: (value: T) => void
+}
+
+const Tab = ({
+ Icon,
+ value,
+ label,
+ isActive,
+ onClick,
+}: TabProps) => {
+ const handleClick = useCallback(() => {
+ onClick(value)
+ }, [onClick, value])
+
+ return (
+
+
+ {label}
+
+ )
+}
+
+export default React.memo(Tab) as typeof Tab
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index 46afd95b97..60bd734f0c 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -10,6 +10,8 @@ import './styles/globals.css'
import './styles/markdown.scss'
import GlobalPublicStoreProvider from '@/context/global-public-context'
import { DatasetAttr } from '@/types/feature'
+import { Instrument_Serif } from 'next/font/google'
+import cn from '@/utils/classnames'
export const viewport: Viewport = {
width: 'device-width',
@@ -19,6 +21,13 @@ export const viewport: Viewport = {
userScalable: false,
}
+const instrumentSerif = Instrument_Serif({
+ weight: ['400'],
+ style: ['normal', 'italic'],
+ subsets: ['latin'],
+ variable: '--font-instrument-serif',
+})
+
const LocaleLayout = async ({
children,
}: {
@@ -51,15 +60,15 @@ const LocaleLayout = async ({
}
return (
-
+
-
-
-
-
+
+
+
+