From 21193c2fbf34027fd204b1715537bcfc3f933994 Mon Sep 17 00:00:00 2001 From: Yi Date: Sat, 14 Sep 2024 17:09:25 +0800 Subject: [PATCH] update the plugins page --- web/app/(commonLayout)/plugins/Container.tsx | 94 ++++++++++++++++++ .../plugins/InstallPluginDropdown.tsx | 67 +++++++++++++ web/app/(commonLayout)/plugins/page.tsx | 13 +++ web/app/components/base/badge/index.css | 28 ++++++ web/app/components/base/badge/index.tsx | 81 +++++++++++++++ .../base/icons/assets/vender/other/group.svg | 8 ++ .../assets/vender/solid/files/file-zip.svg | 6 ++ .../assets/vender/solid/general/github.svg | 5 + .../base/icons/src/vender/other/Group.json | 66 +++++++++++++ .../base/icons/src/vender/other/Group.tsx | 16 +++ .../base/icons/src/vender/other/index.ts | 1 + .../icons/src/vender/solid/files/FileZip.json | 47 +++++++++ .../icons/src/vender/solid/files/FileZip.tsx | 16 +++ .../icons/src/vender/solid/files/index.ts | 1 + .../src/vender/solid/general/Github.json | 36 +++++++ .../icons/src/vender/solid/general/Github.tsx | 16 +++ .../icons/src/vender/solid/general/index.ts | 1 + web/app/components/base/tab-slider/index.tsx | 99 +++++++++++-------- web/app/components/header/index.tsx | 19 +++- .../components/header/plugins-nav/index.tsx | 30 ++++++ web/i18n/en-US/marketplace.ts | 5 + web/service/plugins.ts | 0 22 files changed, 609 insertions(+), 46 deletions(-) create mode 100644 web/app/(commonLayout)/plugins/Container.tsx create mode 100644 web/app/(commonLayout)/plugins/InstallPluginDropdown.tsx create mode 100644 web/app/(commonLayout)/plugins/page.tsx create mode 100644 web/app/components/base/badge/index.css create mode 100644 web/app/components/base/badge/index.tsx create mode 100644 web/app/components/base/icons/assets/vender/other/group.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/files/file-zip.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/general/github.svg create mode 100644 web/app/components/base/icons/src/vender/other/Group.json create mode 100644 web/app/components/base/icons/src/vender/other/Group.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/files/FileZip.json create mode 100644 web/app/components/base/icons/src/vender/solid/files/FileZip.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/general/Github.json create mode 100644 web/app/components/base/icons/src/vender/solid/general/Github.tsx create mode 100644 web/app/components/header/plugins-nav/index.tsx create mode 100644 web/i18n/en-US/marketplace.ts create mode 100644 web/service/plugins.ts diff --git a/web/app/(commonLayout)/plugins/Container.tsx b/web/app/(commonLayout)/plugins/Container.tsx new file mode 100644 index 0000000000..bee44b7215 --- /dev/null +++ b/web/app/(commonLayout)/plugins/Container.tsx @@ -0,0 +1,94 @@ +'use client' + +import { useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiArrowRightUpLine, + RiBugLine, + RiDragDropLine, + RiEqualizer2Line, +} from '@remixicon/react' +import InstallPluginDropdown from './InstallPluginDropdown' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' +import Button from '@/app/components/base/button' +import TabSlider from '@/app/components/base/tab-slider' +import Tooltip from '@/app/components/base/tooltip' + +const Container = () => { + const { t } = useTranslation() + + const options = useMemo(() => { + return [ + { value: 'plugins', text: t('common.menus.plugins') }, + { value: 'discover', text: 'Discover in Marketplace' }, + ] + }, [t]) + + const [activeTab, setActiveTab] = useTabSearchParams({ + defaultTab: 'plugins', + }) + + const containerRef = useRef(null) + + return ( +
+
+
+
+ setActiveTab(newActiveTab)} + options={options} + /> +
+
+ + +
+ Debugging +
+ View docs + +
+
+
+
+ Port +
+
+ + } + popupClassName='flex flex-col items-start w-[256px] px-4 py-3.5 gap-1 border border-components-panel-border + rounded-xl bg-components-tooltip-bg shadows-shadow-lg' + asChild={false} + position='bottom' + > + +
+ +
+
+
+
+
+
+ {/* Content for active tab will go here */} +
+
+
+ + Drop plugin package here to install +
+
+ ) +} + +export default Container diff --git a/web/app/(commonLayout)/plugins/InstallPluginDropdown.tsx b/web/app/(commonLayout)/plugins/InstallPluginDropdown.tsx new file mode 100644 index 0000000000..35b1089bae --- /dev/null +++ b/web/app/(commonLayout)/plugins/InstallPluginDropdown.tsx @@ -0,0 +1,67 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { RiAddLine, RiArrowDownSLine } from '@remixicon/react' +import Button from '@/app/components/base/button' +import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import { FileZip } from '@/app/components/base/icons/src/vender/solid/files' +import { Github } from '@/app/components/base/icons/src/vender/solid/general' + +const InstallPluginDropdown = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false) + const menuRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) + setIsMenuOpen(false) + } + + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, []) + + return ( +
+ + {isMenuOpen && ( +
+ + Install Form + + {[ + { icon: MagicBox, text: 'Marketplace', action: 'marketplace' }, + { icon: Github, text: 'GitHub', action: 'github' }, + { icon: FileZip, text: 'Local Package File', action: 'local' }, + ].map(({ icon: Icon, text, action }) => ( +
{ + console.log(action) + setIsMenuOpen(false) + }} + > + + {text} +
+ ))} +
+ )} +
+ ) +} + +export default InstallPluginDropdown diff --git a/web/app/(commonLayout)/plugins/page.tsx b/web/app/(commonLayout)/plugins/page.tsx new file mode 100644 index 0000000000..c50b3ad9a2 --- /dev/null +++ b/web/app/(commonLayout)/plugins/page.tsx @@ -0,0 +1,13 @@ +import Container from './Container' + +const PluginList = async () => { + return ( + + ) +} + +export const metadata = { + title: 'Plugins - Dify', +} + +export default PluginList diff --git a/web/app/components/base/badge/index.css b/web/app/components/base/badge/index.css new file mode 100644 index 0000000000..99db573c9c --- /dev/null +++ b/web/app/components/base/badge/index.css @@ -0,0 +1,28 @@ +@tailwind components; + +@layer components { + .badge { + @apply inline-flex justify-center items-center text-text-tertiary border border-divider-deep + } + + .badge-l { + @apply rounded-md gap-1 min-w-6 + } + + /* m is for the regular button */ + .badge-m { + @apply rounded-md gap-[3px] min-w-5 + } + + .badge-s { + @apply rounded-[5px] gap-0.5 min-w-[18px] + } + + .badge.badge-warning { + @apply text-text-warning border border-text-warning + } + + .badge.badge-accent { + @apply text-text-accent-secondary border border-text-accent-secondary + } +} \ No newline at end of file diff --git a/web/app/components/base/badge/index.tsx b/web/app/components/base/badge/index.tsx new file mode 100644 index 0000000000..88ba026e14 --- /dev/null +++ b/web/app/components/base/badge/index.tsx @@ -0,0 +1,81 @@ +import type { CSSProperties, ReactNode } from 'react' +import React from 'react' +import { type VariantProps, cva } from 'class-variance-authority' +import classNames from '@/utils/classnames' +import './index.css' + +enum BadgeState { + Warning = 'warning', + Accent = 'accent', + Default = '', +} + +const BadgeVariants = cva( + 'badge', + { + variants: { + size: { + s: 'badge-s', + m: 'badge-m', + l: 'badge-l', + }, + }, + defaultVariants: { + size: 'm', + }, + }, +) + +type BadgeProps = { + size?: 's' | 'm' | 'l' + iconOnly?: boolean + uppercase?: boolean + state?: BadgeState + styleCss?: CSSProperties + children?: ReactNode +} & React.HTMLAttributes & VariantProps + +function getBadgeState(state: BadgeState) { + switch (state) { + case BadgeState.Warning: + return 'badge-warning' + case BadgeState.Accent: + return 'badge-accent' + default: + return '' + } +} + +const Badge: React.FC = ({ + className, + size, + state = BadgeState.Default, + iconOnly = false, + uppercase = false, + styleCss, + children, + ...props +}) => { + return ( +
+ {children} +
+ ) +} +Badge.displayName = 'Badge' + +export default Badge +export { Badge, BadgeState, BadgeVariants } diff --git a/web/app/components/base/icons/assets/vender/other/group.svg b/web/app/components/base/icons/assets/vender/other/group.svg new file mode 100644 index 0000000000..90f1e6b89e --- /dev/null +++ b/web/app/components/base/icons/assets/vender/other/group.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/solid/files/file-zip.svg b/web/app/components/base/icons/assets/vender/solid/files/file-zip.svg new file mode 100644 index 0000000000..213606ae49 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/files/file-zip.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/app/components/base/icons/assets/vender/solid/general/github.svg b/web/app/components/base/icons/assets/vender/solid/general/github.svg new file mode 100644 index 0000000000..c7b203ddb2 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/general/github.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/src/vender/other/Group.json b/web/app/components/base/icons/src/vender/other/Group.json new file mode 100644 index 0000000000..078febbc80 --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/Group.json @@ -0,0 +1,66 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "16", + "viewBox": "0 0 14 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Group" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M5.6475 8.0115L0.333496 5.05884V11.3335C0.333491 11.4524 0.365258 11.569 0.425506 11.6715C0.485754 11.7739 0.572294 11.8584 0.676163 11.9162L6.3335 15.0588V9.17684C6.33344 8.93907 6.26981 8.70565 6.14919 8.50075C6.02857 8.29586 5.85536 8.12694 5.6475 8.0115Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_2", + "d": "M7.66699 9.17684V15.0588L13.3243 11.9162C13.4282 11.8584 13.5147 11.7739 13.575 11.6715C13.6352 11.569 13.667 11.4524 13.667 11.3335V5.05884L8.35299 8.0115C8.14513 8.12694 7.97192 8.29586 7.8513 8.50075C7.73068 8.70565 7.66705 8.93907 7.66699 9.17684Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_3", + "d": "M10.1913 2.34351C9.804 3.33351 8.588 4.00017 7 4.00017C5.412 4.00017 4.196 3.33351 3.80867 2.34351L1 3.90417L6.35267 6.87817C6.5507 6.98815 6.77348 7.04586 7 7.04586C7.22652 7.04586 7.4493 6.98815 7.64733 6.87817L13 3.90417L10.1913 2.34351Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_4", + "d": "M7 2.66675C8.10457 2.66675 9 2.21903 9 1.66675C9 1.11446 8.10457 0.666748 7 0.666748C5.89543 0.666748 5 1.11446 5 1.66675C5 2.21903 5.89543 2.66675 7 2.66675Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "Group" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/other/Group.tsx b/web/app/components/base/icons/src/vender/other/Group.tsx new file mode 100644 index 0000000000..ba9d130f80 --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/Group.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Group.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Group' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/other/index.ts b/web/app/components/base/icons/src/vender/other/index.ts index db6fd9e3f1..0b722bd492 100644 --- a/web/app/components/base/icons/src/vender/other/index.ts +++ b/web/app/components/base/icons/src/vender/other/index.ts @@ -1 +1,2 @@ export { default as Generator } from './Generator' +export { default as Group } from './Group' diff --git a/web/app/components/base/icons/src/vender/solid/files/FileZip.json b/web/app/components/base/icons/src/vender/solid/files/FileZip.json new file mode 100644 index 0000000000..11fe823916 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/files/FileZip.json @@ -0,0 +1,47 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M3.99999 1.33325H7.99999V5.33325C7.99999 6.06963 8.59692 6.66659 9.33332 6.66659H13.3333V13.3333C13.3333 14.0697 12.7364 14.6666 12 14.6666H6.66666V13.3333H7.99999V11.9999H6.66666V10.6666H7.99999V9.33325H6.66666V7.99992H5.33332V9.33325H6.66666V10.6666H5.33332V11.9999H6.66666V13.3333H5.33332V14.6666H3.99999C3.26361 14.6666 2.66666 14.0697 2.66666 13.3333V2.66659C2.66666 1.93021 3.26361 1.33325 3.99999 1.33325Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_2", + "opacity": "0.5", + "d": "M12.9428 4.99993C13.0415 5.09868 13.1232 5.21133 13.1859 5.33327H9.33334V1.48071C9.45528 1.54338 9.56794 1.62504 9.66668 1.72379L12.9428 4.99993Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "FileZip" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/files/FileZip.tsx b/web/app/components/base/icons/src/vender/solid/files/FileZip.tsx new file mode 100644 index 0000000000..675fa0ac98 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/files/FileZip.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './FileZip.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'FileZip' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/files/index.ts b/web/app/components/base/icons/src/vender/solid/files/index.ts index 31feeb5309..fa93cd68dc 100644 --- a/web/app/components/base/icons/src/vender/solid/files/index.ts +++ b/web/app/components/base/icons/src/vender/solid/files/index.ts @@ -1,3 +1,4 @@ export { default as File05 } from './File05' export { default as FileSearch02 } from './FileSearch02' +export { default as FileZip } from './FileZip' export { default as Folder } from './Folder' diff --git a/web/app/components/base/icons/src/vender/solid/general/Github.json b/web/app/components/base/icons/src/vender/solid/general/Github.json new file mode 100644 index 0000000000..46e694215b --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/general/Github.json @@ -0,0 +1,36 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M8 1C4.1325 1 1 4.1325 1 8C1 11.0975 3.00375 13.7137 5.78625 14.6413C6.13625 14.7025 6.2675 14.4925 6.2675 14.3088C6.2675 14.1425 6.25875 13.5913 6.25875 13.005C4.5 13.3288 4.045 12.5763 3.905 12.1825C3.82625 11.9812 3.485 11.36 3.1875 11.1937C2.9425 11.0625 2.5925 10.7387 3.17875 10.73C3.73 10.7212 4.12375 11.2375 4.255 11.4475C4.885 12.5062 5.89125 12.2088 6.29375 12.025C6.355 11.57 6.53875 11.2638 6.74 11.0887C5.1825 10.9137 3.555 10.31 3.555 7.6325C3.555 6.87125 3.82625 6.24125 4.2725 5.75125C4.2025 5.57625 3.9575 4.85875 4.3425 3.89625C4.3425 3.89625 4.92875 3.7125 6.2675 4.61375C6.8275 4.45625 7.4225 4.3775 8.0175 4.3775C8.6125 4.3775 9.2075 4.45625 9.7675 4.61375C11.1063 3.70375 11.6925 3.89625 11.6925 3.89625C12.0775 4.85875 11.8325 5.57625 11.7625 5.75125C12.2087 6.24125 12.48 6.8625 12.48 7.6325C12.48 10.3187 10.8438 10.9137 9.28625 11.0887C9.54 11.3075 9.75875 11.7275 9.75875 12.3837C9.75875 13.32 9.75 14.0725 9.75 14.3088C9.75 14.4925 9.88125 14.7113 10.2312 14.6413C11.6209 14.1721 12.8284 13.279 13.6839 12.0877C14.5393 10.8963 14.9996 9.46668 15 8C15 4.1325 11.8675 1 8 1Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "Github" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/general/Github.tsx b/web/app/components/base/icons/src/vender/solid/general/Github.tsx new file mode 100644 index 0000000000..416743fc71 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/general/Github.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Github.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Github' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/general/index.ts b/web/app/components/base/icons/src/vender/solid/general/index.ts index 9d2492e287..52647905ab 100644 --- a/web/app/components/base/icons/src/vender/solid/general/index.ts +++ b/web/app/components/base/icons/src/vender/solid/general/index.ts @@ -5,6 +5,7 @@ export { default as Download02 } from './Download02' export { default as Edit03 } from './Edit03' export { default as Edit04 } from './Edit04' export { default as Eye } from './Eye' +export { default as Github } from './Github' export { default as MessageClockCircle } from './MessageClockCircle' export { default as PlusCircle } from './PlusCircle' export { default as QuestionTriangle } from './QuestionTriangle' diff --git a/web/app/components/base/tab-slider/index.tsx b/web/app/components/base/tab-slider/index.tsx index 03296a9dee..f8e06935da 100644 --- a/web/app/components/base/tab-slider/index.tsx +++ b/web/app/components/base/tab-slider/index.tsx @@ -1,64 +1,81 @@ import type { FC } from 'react' +import { useEffect, useState } from 'react' import cn from '@/utils/classnames' - +import Badge, { BadgeState } from '@/app/components/base/badge/index' type Option = { value: string text: string } + type TabSliderProps = { className?: string - itemWidth?: number value: string onChange: (v: string) => void options: Option[] } + const TabSlider: FC = ({ className, - itemWidth = 118, value, onChange, options, }) => { - const currentIndex = options.findIndex(option => option.value === value) - const current = options[currentIndex] + const [activeIndex, setActiveIndex] = useState(options.findIndex(option => option.value === value)) + const [sliderStyle, setSliderStyle] = useState({}) + + const updateSliderStyle = (index: number) => { + const tabElement = document.getElementById(`tab-${index}`) + if (tabElement) { + const { offsetLeft, offsetWidth } = tabElement + setSliderStyle({ + transform: `translateX(${offsetLeft}px)`, + width: `${offsetWidth}px`, + }) + } + } + + useEffect(() => { + const newIndex = options.findIndex(option => option.value === value) + setActiveIndex(newIndex) + updateSliderStyle(newIndex) + }, [value, options]) return ( -
- { - options.map((option, index) => ( -
onChange(option.value)} - > - {option.text} -
- )) - } - { - current && ( -
- {current.text} -
- ) - } +
+
+ {options.map((option, index) => ( +
{ + if (index !== activeIndex) { + onChange(option.value) + updateSliderStyle(index) + } + }} + > + {option.text} + {option.value === 'plugins' + && + 6 + + } +
+ ))}
) } diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index 2b020b81e7..035cc6fd1e 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -9,6 +9,7 @@ import AccountDropdown from './account-dropdown' import AppNav from './app-nav' import DatasetNav from './dataset-nav' import EnvNav from './env-nav' +import PluginsNav from './plugins-nav' import ExploreNav from './explore-nav' import ToolsNav from './tools-nav' import GithubStar from './github-star' @@ -59,6 +60,11 @@ const Header = () => { + {enableBilling && ( +
+ +
+ )} }
@@ -67,6 +73,11 @@ const Header = () => { + {enableBilling && ( +
+ +
+ )}
)} @@ -80,11 +91,9 @@ const Header = () => { )}
- {enableBilling && ( -
- -
- )} +
+ +
diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx new file mode 100644 index 0000000000..c2ad9398f4 --- /dev/null +++ b/web/app/components/header/plugins-nav/index.tsx @@ -0,0 +1,30 @@ +'use client' + +import { useTranslation } from 'react-i18next' +import Link from 'next/link' +import classNames from '@/utils/classnames' +import { Group } from '@/app/components/base/icons/src/vender/other' +type PluginsNavProps = { + className?: string +} + +const PluginsNav = ({ + className, +}: PluginsNavProps) => { + const { t } = useTranslation() + + return ( + +
+
+ +
+ {t('common.menus.plugins')} +
+ + ) +} + +export default PluginsNav diff --git a/web/i18n/en-US/marketplace.ts b/web/i18n/en-US/marketplace.ts new file mode 100644 index 0000000000..144fa07cb4 --- /dev/null +++ b/web/i18n/en-US/marketplace.ts @@ -0,0 +1,5 @@ +const translation = { + +} + +export default translation diff --git a/web/service/plugins.ts b/web/service/plugins.ts new file mode 100644 index 0000000000..e69de29bb2