| @@ -36,6 +36,7 @@ import AccessControl from '@/app/components/app/app-access-control' | |||
| import { AccessMode } from '@/models/access-control' | |||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||
| import { formatTime } from '@/utils/time' | |||
| import { useGetUserCanAccessApp } from '@/service/access-control' | |||
| export type AppCardProps = { | |||
| app: App | |||
| @@ -190,6 +191,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { | |||
| }, [onRefresh, mutateApps, setShowAccessControl]) | |||
| const Operations = (props: HtmlContentProps) => { | |||
| const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp } = useGetUserCanAccessApp({ appId: app?.id, enabled: (!!props?.open && systemFeatures.webapp_auth.enabled) }) | |||
| const onMouseLeave = async () => { | |||
| props.onClose?.() | |||
| } | |||
| @@ -267,10 +269,14 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { | |||
| </button> | |||
| </> | |||
| )} | |||
| <Divider className="my-1" /> | |||
| <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickInstalledApp}> | |||
| <span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span> | |||
| </button> | |||
| { | |||
| (isGettingUserCanAccessApp || !userCanAccessApp?.result) ? null : <> | |||
| <Divider className="my-1" /> | |||
| <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickInstalledApp}> | |||
| <span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span> | |||
| </button> | |||
| </> | |||
| } | |||
| <Divider className="my-1" /> | |||
| { | |||
| systemFeatures.webapp_auth.enabled && isCurrentWorkspaceEditor && <> | |||
| @@ -3,6 +3,7 @@ import { Fragment, cloneElement, useRef } from 'react' | |||
| import cn from '@/utils/classnames' | |||
| export type HtmlContentProps = { | |||
| open?: boolean | |||
| onClose?: () => void | |||
| onClick?: () => void | |||
| } | |||
| @@ -100,7 +101,8 @@ export default function CustomPopover({ | |||
| } | |||
| > | |||
| {cloneElement(htmlContent as React.ReactElement, { | |||
| onClose: () => onMouseLeave(open), | |||
| open, | |||
| onClose: close, | |||
| ...(manualClose | |||
| ? { | |||
| onClick: close, | |||
| @@ -6,9 +6,9 @@ import Item from './item' | |||
| import type { Plugin } from '@/app/components/plugins/types.ts' | |||
| import cn from '@/utils/classnames' | |||
| import Link from 'next/link' | |||
| import { MARKETPLACE_URL_PREFIX } from '@/config' | |||
| import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react' | |||
| import { noop } from 'lodash-es' | |||
| import { getMarketplaceUrl } from '@/utils/var' | |||
| export type ListProps = { | |||
| wrapElemRef: React.RefObject<HTMLElement> | |||
| @@ -32,7 +32,7 @@ const List = forwardRef<ListRef, ListProps>(({ | |||
| const { t } = useTranslation() | |||
| const hasFilter = !searchText | |||
| const hasRes = list.length > 0 | |||
| const urlWithSearchText = `${MARKETPLACE_URL_PREFIX}/?q=${searchText}&tags=${tags.join(',')}` | |||
| const urlWithSearchText = getMarketplaceUrl('', { q: searchText, tags: tags.join(',') }) | |||
| const nextToStickyELemRef = useRef<HTMLDivElement>(null) | |||
| const { handleScroll, scrollPosition } = useStickyScroll({ | |||
| @@ -71,7 +71,7 @@ const List = forwardRef<ListRef, ListProps>(({ | |||
| return ( | |||
| <Link | |||
| className='system-sm-medium sticky bottom-0 z-10 flex h-8 cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg' | |||
| href={`${MARKETPLACE_URL_PREFIX}/`} | |||
| href={getMarketplaceUrl('')} | |||
| target='_blank' | |||
| > | |||
| <span>{t('plugin.findMoreInMarketplace')}</span> | |||
| @@ -15,7 +15,7 @@ import { pluginManifestToCardPluginProps } from '@/app/components/plugins/instal | |||
| import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index' | |||
| import Link from 'next/link' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { MARKETPLACE_URL_PREFIX } from '@/config' | |||
| import { getMarketplaceUrl } from '@/utils/var' | |||
| export type SwitchPluginVersionProps = { | |||
| uniqueIdentifier: string | |||
| @@ -82,7 +82,7 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => { | |||
| modalBottomLeft={ | |||
| <Link | |||
| className='flex items-center justify-center gap-1' | |||
| href={`${MARKETPLACE_URL_PREFIX}/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`} | |||
| href={getMarketplaceUrl(`/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`)} | |||
| target='_blank' | |||
| > | |||
| <span className='system-xs-regular text-xs text-text-accent'> | |||
| @@ -29,9 +29,10 @@ export const useTabSearchParams = ({ | |||
| const router = useRouter() | |||
| const pathName = pathnameFromHook || window?.location?.pathname | |||
| const searchParams = useSearchParams() | |||
| const searchParamValue = searchParams.has(searchParamName) ? decodeURIComponent(searchParams.get(searchParamName)!) : defaultTab | |||
| const [activeTab, setTab] = useState<string>( | |||
| !disableSearchParams | |||
| ? (searchParams.get(searchParamName) || defaultTab) | |||
| ? searchParamValue | |||
| : defaultTab, | |||
| ) | |||
| @@ -39,7 +40,7 @@ export const useTabSearchParams = ({ | |||
| setTab(newActiveTab) | |||
| if (disableSearchParams) | |||
| return | |||
| router[`${routingBehavior}`](`${pathName}?${searchParamName}=${newActiveTab}`) | |||
| router[`${routingBehavior}`](`${pathName}?${searchParamName}=${encodeURIComponent(newActiveTab)}`) | |||
| } | |||
| return [activeTab, setActiveTab] as const | |||
| @@ -199,9 +199,9 @@ const translation = { | |||
| accessControl: 'Web App Access Control', | |||
| accessItemsDescription: { | |||
| anyone: 'Anyone can access the web app (no login required)', | |||
| specific: 'Only specific members within the platform can access the Web application', | |||
| organization: 'All members within the platform can access the Web application', | |||
| external: 'Only authenticated external users can access the Web application', | |||
| specific: 'Only specific members within the platform can access the web app', | |||
| organization: 'All members within the platform can access the web app', | |||
| external: 'Only authenticated external users can access the web app', | |||
| }, | |||
| accessControlDialog: { | |||
| title: 'Web App Access Control', | |||
| @@ -218,7 +218,7 @@ const translation = { | |||
| members_one: '{{count}} MEMBER', | |||
| members_other: '{{count}} MEMBERS', | |||
| noGroupsOrMembers: 'No groups or members selected', | |||
| webAppSSONotEnabledTip: 'Please contact your organization administrator to configure external authentication for the Web application.', | |||
| webAppSSONotEnabledTip: 'Please contact your organization administrator to configure external authentication for the web app.', | |||
| operateGroupAndMember: { | |||
| searchPlaceholder: 'Search groups and members', | |||
| allMembers: 'All members', | |||
| @@ -86,5 +86,8 @@ export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled } | |||
| enabled: !!appId && enabled, | |||
| staleTime: 0, | |||
| gcTime: 0, | |||
| initialData: { | |||
| result: !enabled, | |||
| }, | |||
| }) | |||
| } | |||