| @@ -1,6 +1,7 @@ | |||
| 'use client' | |||
| import { useEffect, useRef } from 'react' | |||
| import { useEffect, useRef, useState } from 'react' | |||
| import { useRouter, useSearchParams } from 'next/navigation' | |||
| import useSWRInfinite from 'swr/infinite' | |||
| import { debounce } from 'lodash-es' | |||
| import { useTranslation } from 'react-i18next' | |||
| @@ -10,6 +11,8 @@ import type { AppListResponse } from '@/models/app' | |||
| import { fetchAppList } from '@/service/apps' | |||
| import { useAppContext, useSelector } from '@/context/app-context' | |||
| import { NEED_REFRESH_APP_LIST_KEY } from '@/config' | |||
| import { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations' | |||
| import Confirm from '@/app/components/base/confirm/common' | |||
| const getKey = (pageIndex: number, previousPageData: AppListResponse) => { | |||
| if (!pageIndex || previousPageData.has_more) | |||
| @@ -24,6 +27,11 @@ const Apps = () => { | |||
| const loadingStateRef = useRef(false) | |||
| const pageContainerRef = useSelector(state => state.pageContainerRef) | |||
| const anchorRef = useRef<HTMLAnchorElement>(null) | |||
| const searchParams = useSearchParams() | |||
| const router = useRouter() | |||
| const payProviderName = searchParams.get('provider_name') | |||
| const payStatus = searchParams.get('payment_result') | |||
| const [showPayStatusModal, setShowPayStatusModal] = useState(false) | |||
| useEffect(() => { | |||
| document.title = `${t('app.title')} - Dify` | |||
| @@ -31,6 +39,8 @@ const Apps = () => { | |||
| localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) | |||
| mutate() | |||
| } | |||
| if (payProviderName === ProviderEnum.anthropic && (payStatus === 'succeeded' || payStatus === 'cancelled')) | |||
| setShowPayStatusModal(true) | |||
| }, []) | |||
| useEffect(() => { | |||
| @@ -51,6 +61,11 @@ const Apps = () => { | |||
| return () => pageContainerRef.current?.removeEventListener('scroll', onScroll) | |||
| }, []) | |||
| const handleCancelShowPayStatusModal = () => { | |||
| setShowPayStatusModal(false) | |||
| router.replace('/', { forceOptimisticNavigation: false }) | |||
| } | |||
| return ( | |||
| <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> | |||
| {data?.map(({ data: apps }) => apps.map(app => ( | |||
| @@ -58,6 +73,27 @@ const Apps = () => { | |||
| )))} | |||
| { isCurrentWorkspaceManager | |||
| && <NewAppCard ref={anchorRef} onSuccess={mutate} />} | |||
| { | |||
| showPayStatusModal && ( | |||
| <Confirm | |||
| isShow | |||
| onCancel={handleCancelShowPayStatusModal} | |||
| onConfirm={handleCancelShowPayStatusModal} | |||
| type={ | |||
| payStatus === 'succeeded' | |||
| ? 'success' | |||
| : 'danger' | |||
| } | |||
| title={ | |||
| payStatus === 'succeeded' | |||
| ? t('common.actionMsg.paySucceeded') | |||
| : t('common.actionMsg.payCancelled') | |||
| } | |||
| showOperateCancel={false} | |||
| confirmText={(payStatus === 'cancelled' && t('common.operation.ok')) || ''} | |||
| /> | |||
| ) | |||
| } | |||
| </nav> | |||
| ) | |||
| } | |||
| @@ -1,3 +1,7 @@ | |||
| .wrapper { | |||
| .wrapper-danger { | |||
| background: linear-gradient(180deg, rgba(217, 45, 32, 0.05) 0%, rgba(217, 45, 32, 0.00) 24.02%), #F9FAFB; | |||
| } | |||
| .wrapper-success { | |||
| background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0.00) 22.44%), #F9FAFB; | |||
| } | |||
| @@ -5,6 +5,7 @@ import s from './common.module.css' | |||
| import Modal from '@/app/components/base/modal' | |||
| import { XClose } from '@/app/components/base/icons/src/vender/line/general' | |||
| import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' | |||
| import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' | |||
| import Button from '@/app/components/base/button' | |||
| type ConfirmCommonProps = { | |||
| @@ -13,7 +14,10 @@ type ConfirmCommonProps = { | |||
| onCancel: () => void | |||
| title: string | |||
| desc?: string | |||
| onConfirm: () => void | |||
| onConfirm?: () => void | |||
| showOperate?: boolean | |||
| showOperateCancel?: boolean | |||
| confirmText?: string | |||
| } | |||
| const ConfirmCommon: FC<ConfirmCommonProps> = ({ | |||
| @@ -23,6 +27,9 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({ | |||
| title, | |||
| desc, | |||
| onConfirm, | |||
| showOperate = true, | |||
| showOperateCancel = true, | |||
| confirmText, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| @@ -31,11 +38,15 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({ | |||
| icon: <AlertCircle className='w-6 h-6 text-[#D92D20]' />, | |||
| confirmText: t('common.operation.remove'), | |||
| }, | |||
| success: { | |||
| icon: <CheckCircle className='w-6 h-6 text-[#039855]' />, | |||
| confirmText: t('common.operation.ok'), | |||
| }, | |||
| } | |||
| return ( | |||
| <Modal isShow={isShow} onClose={() => {}} className='!w-[480px] !max-w-[480px] !p-0 !rounded-2xl'> | |||
| <div className={cn(s.wrapper, 'relative p-8')}> | |||
| <div className={cn(s[`wrapper-${type}`], 'relative p-8')}> | |||
| <div className='flex items-center justify-center absolute top-4 right-4 w-8 h-8 cursor-pointer' onClick={onCancel}> | |||
| <XClose className='w-4 h-4 text-gray-500' /> | |||
| </div> | |||
| @@ -46,21 +57,29 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({ | |||
| { | |||
| desc && <div className='mt-1 text-sm text-gray-500'>{desc}</div> | |||
| } | |||
| <div className='flex items-center justify-end mt-10'> | |||
| <Button | |||
| className='mr-2 min-w-24 text-sm font-medium !text-gray-700' | |||
| onClick={onCancel} | |||
| > | |||
| {t('common.operation.cancel')} | |||
| </Button> | |||
| <Button | |||
| type='primary' | |||
| className='' | |||
| onClick={onConfirm} | |||
| > | |||
| {CONFIRM_MAP[type].confirmText} | |||
| </Button> | |||
| </div> | |||
| { | |||
| showOperate && ( | |||
| <div className='flex items-center justify-end mt-10'> | |||
| { | |||
| showOperateCancel && ( | |||
| <Button | |||
| className='mr-2 min-w-24 text-sm font-medium !text-gray-700' | |||
| onClick={onCancel} | |||
| > | |||
| {t('common.operation.cancel')} | |||
| </Button> | |||
| ) | |||
| } | |||
| <Button | |||
| type='primary' | |||
| className='' | |||
| onClick={onConfirm} | |||
| > | |||
| {confirmText || CONFIRM_MAP[type].confirmText} | |||
| </Button> | |||
| </div> | |||
| ) | |||
| } | |||
| </div> | |||
| </Modal> | |||
| ) | |||
| @@ -6,6 +6,7 @@ import Tooltip from '@/app/components/base/tooltip' | |||
| import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' | |||
| import { getPayUrl } from '@/service/common' | |||
| import Button from '@/app/components/base/button' | |||
| import { formatNumber } from '@/utils/format' | |||
| type QuotaProps = { | |||
| currentProvider: Provider | |||
| @@ -49,10 +50,10 @@ const Quota: FC<QuotaProps> = ({ | |||
| const renderQuota = () => { | |||
| if (systemPaid?.is_valid) | |||
| return systemPaid.quota_limit - systemPaid.quota_used | |||
| return formatNumber(systemPaid.quota_limit - systemPaid.quota_used) | |||
| if (systemTrial.is_valid) | |||
| return systemTrial.quota_limit - systemTrial.quota_used | |||
| return formatNumber(systemTrial.quota_limit - systemTrial.quota_used) | |||
| } | |||
| const renderUnit = () => { | |||
| if (systemPaid?.is_valid) | |||
| @@ -28,6 +28,7 @@ const translation = { | |||
| setup: 'Setup', | |||
| getForFree: 'Get for free', | |||
| reload: 'Reload', | |||
| ok: 'OK', | |||
| }, | |||
| placeholder: { | |||
| input: 'Please enter', | |||
| @@ -41,6 +42,8 @@ const translation = { | |||
| modifiedSuccessfully: 'Modified successfully', | |||
| modificationFailed: 'Modification failed', | |||
| copySuccessfully: 'Copied successfully', | |||
| paySucceeded: 'Payment succeeded', | |||
| payCancelled: 'Payment cancelled', | |||
| }, | |||
| model: { | |||
| params: { | |||
| @@ -28,6 +28,7 @@ const translation = { | |||
| setup: '设置', | |||
| getForFree: '免费获取', | |||
| reload: '刷新', | |||
| ok: '好的', | |||
| }, | |||
| placeholder: { | |||
| input: '请输入', | |||
| @@ -41,6 +42,8 @@ const translation = { | |||
| modifiedSuccessfully: '修改成功', | |||
| modificationFailed: '修改失败', | |||
| copySuccessfully: '复制成功', | |||
| paySucceeded: '已支付成功', | |||
| payCancelled: '已取消支付', | |||
| }, | |||
| model: { | |||
| params: { | |||