Compare commits

...

12 Commits

Author SHA1 Message Date
Joel
7b88f09ee7 chore: move apps tips to the app page 2025-07-03 13:58:04 +08:00
Joel
81578f1705 feat: bottom tooltip 2025-07-03 11:46:46 +08:00
Joel
964ce48cbc chore: tip 2025-07-03 10:56:54 +08:00
Joel
779daceeb2 chore: layout 2025-07-03 10:35:34 +08:00
Joel
a294773dd5 temp: tip 2025-07-02 18:33:27 +08:00
Joel
859b4cd6cc feat: list res 2025-07-02 18:26:57 +08:00
Joel
d454214ddc fix: table placeholder 2025-07-02 16:18:44 +08:00
Joel
169ae55635 chore: finish filter 2025-07-02 15:52:43 +08:00
Joel
015ce09593 chore: handle published filter 2025-07-02 15:22:38 +08:00
Joel
0337b857ff feat: search 2025-07-02 14:56:57 +08:00
Joel
d6fe22b19e feat: page holder 2025-07-01 17:57:49 +08:00
Joel
2c4239c593 feat: page holder 2025-06-30 16:51:46 +08:00
10 changed files with 355 additions and 5 deletions

View File

@@ -0,0 +1,44 @@
'use client'
import { RiArrowRightLine, RiImage2Fill } from '@remixicon/react'
import Link from 'next/link'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
const i18nPrefix = 'app.checkLegacy'
type Props = {
appNum: number,
publishedNum: number,
}
const AppTip: FC<Props> = ({
appNum,
publishedNum,
}) => {
const { t } = useTranslation()
return (
<div className='fixed bottom-0 left-0 right-0 z-10 border-t border-state-warning-hover px-12 py-4'>
<div className="absolute inset-0 bg-[linear-gradient(92deg,_rgba(247,144,9,0.25)_53.67%,_rgba(255,255,255,0)_100%)] opacity-40" />
<div className='relative flex items-center'>
<div className='relative rounded-lg bg-text-accent p-1.5'>
<RiImage2Fill className='size-5 text-text-primary-on-surface' />
<div className='absolute left-[-2px] top-[-2px] size-2 rounded-[3px] border border-white bg-components-badge-status-light-error-border-inner p-0.5'>
<div className='h-full w-full rounded-[3px] bg-components-badge-status-light-error-bg'></div>
</div>
</div>
<div className='ml-3'>
<div className='system-md-semibold text-text-primary'>{t(`${i18nPrefix}.title`)}</div>
<div className='system-sm-regular mt-1 flex items-center space-x-0.5 text-text-secondary'>
{t(`${i18nPrefix}.description`, { num: appNum, publishedNum })}
<Link className='system-sm-semibold text-text-accent' href='/apps/check-legacy'>{t(`${i18nPrefix}.toSolve`)}</Link>
<RiArrowRightLine className='size-4 text-components-button-secondary-accent-text' />
</div>
</div>
</div>
</div>
)
}
export default React.memo(AppTip)

View File

@@ -0,0 +1,29 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
const i18nPrefix = 'app.checkLegacy'
type Props = {
className?: string,
appNum: number,
publishedNum: number,
}
const Header: FC<Props> = ({
className,
appNum,
publishedNum,
}) => {
const { t } = useTranslation()
return (
<div className={cn(className)}>
<div className='title-2xl-semi-bold text-text-primary'>{t(`${i18nPrefix}.title`)}</div>
<div className='system-md-regular mt-1 text-text-tertiary'>{t(`${i18nPrefix}.description`, { num: appNum, publishedNum })}</div>
</div>
)
}
export default React.memo(Header)

View File

@@ -0,0 +1,53 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
import AppIcon from '@/app/components/base/app-icon'
const i18nPrefix = 'app.checkLegacy.list'
type Props = {
list: any[]
}
const List: FC<Props> = ({
list,
}) => {
const { t } = useTranslation()
return (
<div className='h-0 grow overflow-y-auto'>
{list.length > 0 ? (
<table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}>
<thead className='system-xs-medium-uppercase text-text-tertiary'>
<tr>
<td className='whitespace-nowrap rounded-l-lg bg-background-section-burn pl-3 pr-1'>{t(`${i18nPrefix}.appName`)}</td>
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t(`${i18nPrefix}.published`)}</td>
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t(`${i18nPrefix}.createBy`)}</td>
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t(`${i18nPrefix}.lastRequest`)}</td>
<td className='whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t(`${i18nPrefix}.createAt`)}</td>
</tr>
</thead>
<tbody className="system-sm-regular text-text-secondary">
{list.map((item, index) => (
<tr key={index} className='cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover'>
<td className='whitespace-nowrap rounded-l-lg py-2 pl-3 pr-1'>
<div className='flex items-center space-x-2'>
<AppIcon size='tiny' />
<div>Python bug fixer</div>
</div>
</td>
<td className='whitespace-nowrap py-1.5 pl-3'>{t('app.checkLegacy.yes')}</td>
<td className='whitespace-nowrap py-1.5 pl-3'>Evan · evan@dify.ai</td>
<td className='whitespace-nowrap py-1.5 pl-3'>2023-03-21 10:25</td>
<td className='whitespace-nowrap rounded-r-lg py-1.5 pl-3'>2023-03-21 10:25</td>
</tr>
))}
</tbody>
</table>
) : (
<div className='system-md-regular flex items-center justify-center text-text-secondary'>{t(`${i18nPrefix}.noData`)}</div>
)}
</div>
)
}
export default React.memo(List)

View File

@@ -0,0 +1,23 @@
'use client'
import { RiBookOpenLine } from '@remixicon/react'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
const i18nPrefix = 'app.checkLegacy.tip'
const Tip: FC = () => {
const { t } = useTranslation()
return (
<div className='w-[316px] rounded-xl bg-background-section p-6'>
<div className='inline-flex rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-border p-2 shadow-lg backdrop-blur-[5px]'>
<RiBookOpenLine className='size-5 text-text-accent' />
</div>
<div className='system-xl-semibold mt-3 text-text-primary'>{t(`${i18nPrefix}.title`)}</div>
<div className='system-sm-regular mt-2 text-text-secondary'>{t(`${i18nPrefix}.description`)}</div>
<a className='system-sm-medium mt-2 text-text-accent' target='_blank' href='todo'>{t(`${i18nPrefix}.learnMore`)}</a>
</div>
)
}
export default React.memo(Tip)

View File

@@ -0,0 +1,100 @@
'use client'
import Sort from '@/app/components/base/sort'
import Header from './components/header'
import List from './components/list'
import useLegacyList from './use-legacy-list'
import Chip from '@/app/components/base/chip'
import { RiFilter3Line, RiLoopLeftLine } from '@remixicon/react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Pagination from '@/app/components/base/pagination'
import { APP_PAGE_LIMIT } from '@/config'
import { noop } from 'lodash'
import Tip from './components/tip'
const i18nPrefix = 'app.checkLegacy'
const Page = () => {
const { t } = useTranslation()
const {
list,
total,
sort_by,
setOrderBy,
published,
setPublished,
clearPublished,
} = useLegacyList()
const handleSelectPublished = useCallback(({ value }: { value: number }) => {
setPublished(value)
}, [setPublished])
const renderTriggerContent = useCallback(() => {
if(published === undefined)
return t(`${i18nPrefix}.published`)
return (
<div className='flex space-x-1'>
<div>{t(`${i18nPrefix}.published`)}</div>
<span className='system-sm-medium text-text-secondary'>{published === 1 ? t(`${i18nPrefix}.yes`) : t(`${i18nPrefix}.no`)}</span>
</div>
)
}, [published, t])
return (
<div className='flex grow rounded-t-2xl border-t border-effects-highlight bg-background-default-subtle px-6 pt-4'>
<div className='flex h-full grow flex-col pr-6'>
<Header className='shrink-0' appNum={5} publishedNum={3}/>
{/* Filter */}
<div className='mb-2 mt-4 flex shrink-0 items-center justify-between'>
<div className='flex items-center gap-2'>
<Chip
className='min-w-[150px]'
panelClassName='w-[270px]'
leftIcon={<RiFilter3Line className='h-4 w-4 text-text-secondary' />}
value={published}
renderTriggerContent={renderTriggerContent}
onSelect={handleSelectPublished}
onClear={clearPublished}
items={[
{ value: 1, name: t(`${i18nPrefix}.yes`) },
{ value: 0, name: t(`${i18nPrefix}.no`) },
]}
/>
<div className='h-3.5 w-px bg-divider-regular'></div>
<Sort
// '-' means descending order
order={sort_by?.startsWith('-') ? '-' : ''}
value={sort_by?.replace('-', '') || 'created_at'}
items={[
{ value: 'created_at', name: t(`${i18nPrefix}.createAt`) },
{ value: 'last_request', name: t(`${i18nPrefix}.lastRequest`) },
]}
onSelect={setOrderBy}
/>
</div>
<Button >
<RiLoopLeftLine className='mr-1 h-4 w-4' />
{t('common.operation.reset')}
</Button>
</div>
<List list={list} />
{(total && total > APP_PAGE_LIMIT)
? <div className='flex justify-end'><Pagination
className='shrink-0'
current={1}
onChange={noop}
total={total}
limit={10}
onLimitChange={noop}
/></div>
: null}
</div>
<div className='ml-3 shrink-0 pr-8 pt-[108px]'>
<Tip />
</div>
</div>
)
}
export default Page

View File

@@ -0,0 +1,43 @@
import produce from 'immer'
import { useCallback, useState } from 'react'
const useLegacyList = () => {
const list: any[] = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}] // Placeholder for the list, replace with actual data fetching logic
const [queryParams, setQueryParams] = useState<Record<string, any>>({})
const {
sort_by,
published,
} = queryParams
const setOrderBy = useCallback((sortBy: string) => {
const nextValue = produce(queryParams, (draft) => {
draft.sort_by = sortBy
})
setQueryParams(nextValue)
}, [queryParams])
const setPublished = useCallback((value: number) => {
const nextValue = produce(queryParams, (draft) => {
draft.published = value
})
setQueryParams(nextValue)
}, [queryParams])
const clearPublished = useCallback(() => {
const nextValue = produce(queryParams, (draft) => {
draft.published = undefined
})
setQueryParams(nextValue)
}, [queryParams])
return {
total: 5,
list,
sort_by,
setOrderBy,
published,
setPublished,
clearPublished,
}
}
export default useLegacyList

View File

@@ -6,15 +6,19 @@ import style from '../list.module.css'
import Apps from './Apps'
import { useEducationInit } from '@/app/education-apply/hooks'
import { useGlobalPublicStore } from '@/context/global-public-context'
import AppTip from './check-legacy/components/app-tip'
import cn from '@/utils/classnames'
const AppList = () => {
const { t } = useTranslation()
useEducationInit()
const { systemFeatures } = useGlobalPublicStore()
const legacyAppNum = 5
const publishedAppNum = 3
return (
<div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'>
<div className={cn('relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body', legacyAppNum > 0 && 'pb-[74px]')}>
<Apps />
{!systemFeatures.branding.enabled && <footer className='shrink-0 grow-0 px-12 py-6'>
{(!systemFeatures.branding.enabled && !legacyAppNum) && <footer className='shrink-0 grow-0 px-12 py-6'>
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3>
<p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p>
<div className='mt-3 flex items-center gap-2'>
@@ -26,6 +30,10 @@ const AppList = () => {
</Link>
</div>
</footer>}
{legacyAppNum > 0 && (
<AppTip appNum={legacyAppNum} publishedNum={publishedAppNum} />
)}
</div >
)
}

View File

@@ -1,5 +1,5 @@
import type { FC } from 'react'
import { useMemo, useState } from 'react'
import React, { useMemo, useState } from 'react'
import { RiArrowDownSLine, RiCheckLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react'
import cn from '@/utils/classnames'
import {
@@ -20,6 +20,7 @@ type Props = {
leftIcon?: any
value: number | string
items: Item[]
renderTriggerContent?: () => React.ReactNode
onSelect: (item: any) => void
onClear: () => void
}
@@ -30,14 +31,17 @@ const Chip: FC<Props> = ({
leftIcon,
value,
items,
renderTriggerContent,
onSelect,
onClear,
}) => {
const [open, setOpen] = useState(false)
const triggerContent = useMemo(() => {
if(renderTriggerContent)
return renderTriggerContent()
return items.find(item => item.value === value)?.name || ''
}, [items, value])
}, [items, renderTriggerContent, value])
return (
<PortalToFollowElem
@@ -71,7 +75,7 @@ const Chip: FC<Props> = ({
</div>
</div>
{!value && <RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />}
{!!value && (
{(!!value || value === 0) && (
<div className='group/clear cursor-pointer p-[1px]' onClick={(e) => {
e.stopPropagation()
onClear()

View File

@@ -233,6 +233,29 @@ const translation = {
notSetDesc: 'Currently nobody can access the web app. Please set permissions.',
},
noAccessPermission: 'No permission to access web app',
checkLegacy: {
title: 'Apps affected by image upload legacy',
description: 'The current workspace has {{num}} applications affected, {{publishedNum}} of which have been published.',
published: 'Published',
yes: 'Yes',
no: 'No',
createAt: 'Created At',
lastRequest: 'Last Request',
list: {
appName: 'App Name',
published: 'Published',
createBy: 'Created By',
lastRequest: 'Last Request',
createAt: 'Created At',
noData: 'No items found',
},
tip: {
title: 'How to solve it?',
description: 'You can now create file type variables in the start form. We will no longer support the image upload feature in the future.',
learnMore: 'Learn more',
},
toSolve: 'To resolve',
},
}
export default translation

View File

@@ -234,6 +234,29 @@ const translation = {
notSetDesc: '当前任何人都无法访问 Web 应用。请设置访问权限。',
},
noAccessPermission: '没有权限访问 web 应用',
checkLegacy: {
title: '受图像上传遗留问题影响的应用',
description: '当前工作区有 {{num}} 个应用受图像上传遗留问题影响,其中 {{publishedNum}} 个已发布。',
published: '已发布',
yes: '是',
no: '否',
createAt: '创建时间',
lastRequest: '最后请求',
list: {
appName: '应用名称',
published: '已发布',
createBy: '创建者',
lastRequest: '最后请求时间',
createAt: '创建时间',
noData: '暂无数据',
},
tip: {
title: '如何解决?',
description: '您现在可以在开始表单中创建文件类型变量。未来我们将不再支持图片上传功能。',
learnMore: '了解更多',
},
toSolve: '去解决',
},
}
export default translation