diff --git a/apps/builder/package.json b/apps/builder/package.json index 07a0b5392f..f10b39bd8c 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -5,7 +5,7 @@ "private": true, "author": "ILLA Cloud ", "license": "Apache-2.0", - "version": "4.3.6", + "version": "4.3.7", "scripts": { "dev": "vite --strictPort --force", "build-cloud": "NODE_OPTIONS=--max-old-space-size=12288 vite build --mode cloud", diff --git a/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/index.tsx b/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/index.tsx new file mode 100644 index 0000000000..d43a56750d --- /dev/null +++ b/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/index.tsx @@ -0,0 +1,79 @@ +import IconHotSpot from "@illa-public/icon-hot-spot" +import { FC } from "react" +import { createPortal } from "react-dom" +import { useTranslation } from "react-i18next" +import { AddIcon, CloseIcon, Modal, getColor } from "@illa-design/react" +import { + closeIconStyle, + containerStyle, + createOptionsContainerStyle, + descStyle, + headerStyle, + iconStyle, + operateContainerStyle, + titleStyle, +} from "./style" + +interface CreateModalProps { + closeGuideCreateAppModal: () => void + openCreateFromResourceModal: () => void + openCreateFromTemplateModal: () => void +} + +const CreateModal: FC = ({ + closeGuideCreateAppModal, + openCreateFromResourceModal, + openCreateFromTemplateModal, +}) => { + const { t } = useTranslation() + return createPortal( + +
+ + + + + +
+ + {t("editor.tutorial.panel.onboarding_app.congratulations_title")} + + + {t("new_dashboard.create_new.onboarding_cloud")} + +
+
+
+ + + + {t("new_dashboard.create_new.create_from_template")} +
+
+ + + + {t("new_dashboard.create_new.generate_crud_short")} +
+
+
+
, + document.body, + ) +} + +export default CreateModal diff --git a/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/style.ts b/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/style.ts new file mode 100644 index 0000000000..55460a7afa --- /dev/null +++ b/apps/builder/src/components/Guide/GuideCreateApp/CreateModal/style.ts @@ -0,0 +1,79 @@ +import { css } from "@emotion/react" +import { getColor } from "@illa-design/react" + +export const containerStyle = css` + display: flex; + flex-direction: column; + gap: 16px; + padding: 24px; + position: relative; +` + +export const headerStyle = css` + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; +` + +export const closeIconStyle = css` + position: absolute; + right: 24px; + top: 24px; + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; +` + +export const titleStyle = css` + color: ${getColor("grayBlue", "02")}; + font-size: 16px; + font-weight: 500; + line-height: 24px; +` + +export const descStyle = css` + color: ${getColor("grayBlue", "03")}; + font-size: 14px; + font-weight: 400; + line-height: 22px; +` + +export const operateContainerStyle = css` + width: 100%; + display: flex; + gap: 16px; +` + +export const createOptionsContainerStyle = (bgColor: string) => css` + display: flex; + color: ${getColor("white", "01")}; + border-radius: 8px; + align-items: center; + background-color: ${bgColor}; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 22px; + cursor: pointer; + height: 118px; + width: 200px; + padding: 24px; + flex-direction: column; + text-align: center; + gap: 8px; +` +export const iconStyle = css` + display: flex; + padding: 12px; + width: 40px; + height: 40px; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 50%; + background-color: ${getColor("white", "07")}; + position: relative; +` diff --git a/apps/builder/src/components/Guide/GuideCreateApp/index.tsx b/apps/builder/src/components/Guide/GuideCreateApp/index.tsx new file mode 100644 index 0000000000..c35734cfc7 --- /dev/null +++ b/apps/builder/src/components/Guide/GuideCreateApp/index.tsx @@ -0,0 +1,171 @@ +import { + BuildActionInfo, + CreateFromResourceModal, + CreateFromTemplateModal, + REPORT_PARAMETER, + REPORT_TEMPLATE_STATUS, + RESOURCE_TYPE, + fetchBatchCreateAction, +} from "@illa-public/create-app" +import { + ILLA_MIXPANEL_BUILDER_PAGE_NAME, + ILLA_MIXPANEL_EVENT_TYPE, + MixpanelTrackProvider, +} from "@illa-public/mixpanel-utils" +import { ComponentTreeNode, Resource } from "@illa-public/public-types" +import { getAuthToken } from "@illa-public/utils" +import { AnimatePresence } from "framer-motion" +import { FC, useCallback, useState } from "react" +import { useTranslation } from "react-i18next" +import { useDispatch, useSelector } from "react-redux" +import { useParams } from "react-router-dom" +import { useMessage } from "@illa-design/react" +import { guideActions } from "@/redux/guide/guideSlice" +import { getAllResources } from "@/redux/resource/resourceSelector" +import { resourceActions } from "@/redux/resource/resourceSlice" +import { fetchCreateApp, fetchForkApp } from "@/services/apps" +import { resourceContextHelper, track } from "@/utils/mixpanelHelper" +import { getCurrentTeamID } from "@/utils/team" +import CreateModal from "./CreateModal" + +const GuideCreateApp: FC = () => { + const message = useMessage() + const teamID = useSelector(getCurrentTeamID)! + const { teamIdentifier } = useParams() + const resourceList = useSelector(getAllResources) || [] + const dispatch = useDispatch() + const { t } = useTranslation() + const [showCreateFromResourceModal, setShowCreateFromResourceModal] = + useState(false) + + const [showCreateFromTemplateModal, setShowCreateFromTemplateModal] = + useState(false) + + const closeGuideCreateAppModal = () => { + dispatch(guideActions.updateGuideStatusReducer(false)) + } + + const forkApp = async (appID: string) => { + track?.( + ILLA_MIXPANEL_EVENT_TYPE.CLICK, + ILLA_MIXPANEL_BUILDER_PAGE_NAME.TUTORIAL, + { + element: "create_app_modal_use_template", + parameter1: REPORT_PARAMETER.BLANK_TUTORIAL_APP, + }, + ) + try { + const resp = await fetchForkApp(appID) + window.open( + `${import.meta.env.ILLA_BUILDER_URL}/${teamIdentifier}/app/${ + resp.data.appId + }?token=${getAuthToken()}`, + "_blank", + ) + } catch (e) { + message.error({ content: t("create_fail") }) + } finally { + closeGuideCreateAppModal() + } + } + + const handleUpdateResource = useCallback( + (resource: Resource) => { + dispatch(resourceActions.addResourceItemReducer(resource)) + }, + [dispatch], + ) + + const createFromResource = async ( + appInfo: ComponentTreeNode, + actionsInfo: BuildActionInfo[], + ) => { + try { + const resp = await fetchCreateApp({ + appName: "Untitled app", + initScheme: appInfo, + }) + await fetchBatchCreateAction(teamID, resp.data.appId, actionsInfo) + window.open( + `${import.meta.env.ILLA_BUILDER_URL}/${teamIdentifier}/app/${ + resp.data.appId + }?token=${getAuthToken()}`, + "_blank", + ) + } catch (e) { + message.error({ content: t("create_fail") }) + } finally { + closeGuideCreateAppModal() + } + } + + return ( + + { + track?.( + ILLA_MIXPANEL_EVENT_TYPE.CLICK, + ILLA_MIXPANEL_BUILDER_PAGE_NAME.TUTORIAL, + { + element: "create_app_modal_db", + parameter1: REPORT_PARAMETER.BLANK_TUTORIAL_APP, + }, + ) + setShowCreateFromResourceModal(true) + }} + openCreateFromTemplateModal={() => { + track?.( + ILLA_MIXPANEL_EVENT_TYPE.SHOW, + ILLA_MIXPANEL_BUILDER_PAGE_NAME.TUTORIAL, + { + element: "create_app_modal", + parameter1: REPORT_PARAMETER.CREATE_APP_MODAL, + }, + ) + setShowCreateFromTemplateModal(true) + }} + /> + {showCreateFromResourceModal && ( + + + Object.values(RESOURCE_TYPE).includes( + item?.resourceType as RESOURCE_TYPE, + ), + )} + createCallBack={createFromResource} + closeModal={() => setShowCreateFromResourceModal(false)} + /> + + )} + {showCreateFromTemplateModal && ( + { + track?.( + ILLA_MIXPANEL_EVENT_TYPE.CLICK, + ILLA_MIXPANEL_BUILDER_PAGE_NAME.TUTORIAL, + { + element: "create_app_modal_use_template", + parameter1: REPORT_PARAMETER.CREATE_APP_MODAL, + parameter2: REPORT_TEMPLATE_STATUS.IS_MODAL_TEMPLATE, + parameter5: appID, + }, + ) + await forkApp(appID) + }} + closeModal={() => setShowCreateFromTemplateModal(false)} + /> + )} + + ) +} + +export default GuideCreateApp diff --git a/apps/builder/src/components/Guide/GuidePopover/index.tsx b/apps/builder/src/components/Guide/GuidePopover/index.tsx index 6aa0be6744..a9b607991c 100644 --- a/apps/builder/src/components/Guide/GuidePopover/index.tsx +++ b/apps/builder/src/components/Guide/GuidePopover/index.tsx @@ -79,8 +79,8 @@ export const GuidePopover: FC = (props) => { {isLastStep ? t("editor.tutorial.panel.onboarding_app.congratulations_button") : hideExit - ? t("editor.tutorial.panel.onboarding_app.test_it_button") - : t("editor.tutorial.panel.onboarding_app.do_it")} + ? t("editor.tutorial.panel.onboarding_app.test_it_button") + : t("editor.tutorial.panel.onboarding_app.do_it")} diff --git a/apps/builder/src/components/Guide/index.tsx b/apps/builder/src/components/Guide/index.tsx index 131fde15db..270e7fa5f5 100644 --- a/apps/builder/src/components/Guide/index.tsx +++ b/apps/builder/src/components/Guide/index.tsx @@ -1,4 +1,5 @@ import { Global } from "@emotion/react" +import { isCloudVersion } from "@illa-public/utils" import { FC, RefObject, useEffect, useRef, useState } from "react" import { createPortal } from "react-dom" import { useSelector } from "react-redux" @@ -13,6 +14,7 @@ import { } from "@/components/Guide/style" import { GUIDE_STEP } from "@/config/guide/config" import { getCurrentStep } from "@/redux/guide/guideSelector" +import GuideCreateApp from "./GuideCreateApp" export interface GuideProps { canvasRef: RefObject @@ -72,9 +74,13 @@ export const Guide: FC = (props) => { createPortal(, currentElement)} {/* success tip */} {currentStep === 12 && } - {currentStep === 12 && currentElement && ( - - )} + {currentStep === 12 && currentElement ? ( + isCloudVersion ? ( + + ) : ( + + ) + ) : null} ) } diff --git a/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByDatabase/index.tsx b/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByDatabase/index.tsx index 69e096a9a7..e818637bc5 100644 --- a/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByDatabase/index.tsx +++ b/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByDatabase/index.tsx @@ -99,7 +99,7 @@ const BuildByDatabase: FC = () => { diff --git a/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByTemplate/index.tsx b/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByTemplate/index.tsx index 49def0daa7..22f10eb0b5 100644 --- a/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByTemplate/index.tsx +++ b/apps/builder/src/page/App/Module/CanvasPanel/Components/BuildAppOnEmpty/BuildByTemplate/index.tsx @@ -96,6 +96,14 @@ const BuildByTemplate: FC = ({ }, "both", ) + track?.( + ILLA_MIXPANEL_EVENT_TYPE.SHOW, + { + element: "create_app_modal", + parameter1: REPORT_PARAMETER.MORE_TEMPLATE, + }, + "both", + ) setShowCreateFromTemplateModal(true) } diff --git a/apps/builder/src/page/App/components/InspectPanel/PanelSetters/EventHandlerSetter/List/style.ts b/apps/builder/src/page/App/components/InspectPanel/PanelSetters/EventHandlerSetter/List/style.ts index 983bb830ee..1e0d8ab197 100644 --- a/apps/builder/src/page/App/components/InspectPanel/PanelSetters/EventHandlerSetter/List/style.ts +++ b/apps/builder/src/page/App/components/InspectPanel/PanelSetters/EventHandlerSetter/List/style.ts @@ -57,6 +57,7 @@ export const groupWrapperStyle = css` export const moreIconWrapperStyle = css` width: 32px; + flex: none; height: 32px; border: 1px solid ${globalColor(`--${illaPrefix}-grayBlue-08`)}; border-radius: 0 8px 8px 0; @@ -79,20 +80,27 @@ export const eventAndMethodWrapperStyle = css` padding: 6px 16px; cursor: pointer; font-size: 12px; + gap: 8px; + overflow: hidden; ` export const eventNameStyle = css` color: ${globalColor(`--${illaPrefix}-grayBlue-02`)}; min-width: 64px; - margin-right: 8px; - display: flex; - align-items: center; + display: block; + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; ` export const methodNameStyle = css` color: ${globalColor(`--${illaPrefix}-grayBlue-04`)}; - display: flex; - align-items: center; + display: block; + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; ` export const emptyBodyStyle = css` diff --git a/apps/builder/src/services/apps.ts b/apps/builder/src/services/apps.ts index 66814b1d8d..cb740a728e 100644 --- a/apps/builder/src/services/apps.ts +++ b/apps/builder/src/services/apps.ts @@ -227,3 +227,11 @@ export const fetchCopyApp = (appID: string, name: string) => { }, ) } + +export const fetchForkApp = (appID: string) => { + const teamID = getCurrentTeamID() + return builderRequest<{ appId: string }>({ + url: `/apps/${appID}/forkTo/teams/${teamID}`, + method: "POST", + }) +} diff --git a/apps/builder/src/widgetLibrary/UploadWidget/upload.tsx b/apps/builder/src/widgetLibrary/UploadWidget/upload.tsx index 799778a297..268f2ff642 100644 --- a/apps/builder/src/widgetLibrary/UploadWidget/upload.tsx +++ b/apps/builder/src/widgetLibrary/UploadWidget/upload.tsx @@ -43,7 +43,6 @@ export const WrappedUpload: FC = (props) => { const { selectionType, type, - displayName, showFileList, disabled, fileType = [], @@ -52,83 +51,14 @@ export const WrappedUpload: FC = (props) => { dropText, colorScheme, variant, - parseValue, fileList, onRemove, onChange, - getValidateMessage, - handleUpdateMultiExecutionResult, customRequest, } = props const isDrag = type === "dropzone" const inputAcceptType = fileType.join(",") - const prevFileList = useRef(fileList ?? []) - const prevParseValue = useRef(parseValue ?? false) - - useEffect(() => { - if ( - !fileList || - (prevFileList.current === fileList && - prevParseValue.current === parseValue) - ) { - return - } - prevFileList.current = fileList - prevParseValue.current = parseValue ?? false - new Promise(async (resolve) => { - const values = await Promise.allSettled( - fileList.map(async (file) => await toBase64(file)), - ) - let parsedValues - if (parseValue) { - parsedValues = await Promise.allSettled( - fileList.map(async (file) => { - const res = await getFileString(file) - return res - }), - ) - } - resolve({ - values, - parsedValues, - fileList, - }) - }).then((value) => { - const { - values, - parsedValues, - fileList = [], - } = value as { - values: any[] - parsedValues: any[] - fileList: UploadItem[] - } - const validateMessage = getValidateMessage(fileList) - const base64value = getFilteredValue(values, "base64") - const files = getFiles(fileList, base64value ?? []) - const parsed = getFilteredValue(parsedValues) - const currentList = getCurrentList(fileList) - handleUpdateMultiExecutionResult([ - { - displayName, - value: { - files, - value: base64value, - parsedValue: parsed, - validateMessage, - currentList, - }, - }, - ]) - }) - }, [ - displayName, - parseValue, - fileList, - getValidateMessage, - handleUpdateMultiExecutionResult, - ]) return ( = (props) => { const { + displayName, appendFiles, customRule, tooltipText, @@ -166,6 +97,7 @@ export const UploadWidget: FC = (props) => { currentList, value, files, + parseValue, minSize, validateMessage, triggerEventHandler, @@ -174,6 +106,7 @@ export const UploadWidget: FC = (props) => { handleUpdateDsl, updateComponentRuntimeProps, deleteComponentRuntimeProps, + handleUpdateMultiExecutionResult, } = props const fileListRef = useRef([]) @@ -181,6 +114,9 @@ export const UploadWidget: FC = (props) => { const fileCountRef = useRef(0) const previousValueRef = useRef([]) + const prevFileList = useRef(currentFileList ?? []) + const prevParseValue = useRef(parseValue ?? false) + useEffect(() => { const canInitialDragValue = currentList && @@ -212,6 +148,118 @@ export const UploadWidget: FC = (props) => { return currentFilesKeys.indexOf(file.uid || file.name) }, []) + const getValidateMessage = useCallback( + (value?: UploadItem[]) => { + if (!hideValidationMessage) { + const message = handleValidateCheck({ + value, + minFiles, + maxFiles, + minSize, + maxSize, + sizeType, + required, + customRule, + }) + const showMessage = message && message.length > 0 + return showMessage ? message : "" + } + return "" + }, + [ + customRule, + hideValidationMessage, + minFiles, + maxFiles, + minSize, + sizeType, + maxSize, + required, + ], + ) + + const handleValidate = useCallback( + (value?: UploadItem[]) => { + const message = getValidateMessage(value) + handleUpdateDsl({ + validateMessage: message, + }) + return message + }, + [getValidateMessage, handleUpdateDsl], + ) + + const handleFilesChange = useCallback( + (fileList?: UploadItem[]) => { + if ( + !fileList || + (prevFileList.current === fileList && + prevParseValue.current === parseValue) + ) { + return + } + prevFileList.current = fileList + prevParseValue.current = parseValue ?? false + new Promise(async (resolve) => { + const values = await Promise.allSettled( + fileList.map(async (file) => await toBase64(file)), + ) + let parsedValues + if (parseValue) { + parsedValues = await Promise.allSettled( + fileList.map(async (file) => { + const res = await getFileString(file) + return res + }), + ) + } + resolve({ + values, + parsedValues, + fileList, + }) + }) + .then((value) => { + const { + values, + parsedValues, + fileList = [], + } = value as { + values: any[] + parsedValues: any[] + fileList: UploadItem[] + } + const validateMessage = getValidateMessage(fileList) + const base64value = getFilteredValue(values, "base64") + const files = getFiles(fileList, base64value ?? []) + const parsed = getFilteredValue(parsedValues) + const currentList = getCurrentList(fileList) + handleUpdateMultiExecutionResult([ + { + displayName, + value: { + files, + value: base64value, + parsedValue: parsed, + validateMessage, + currentList, + }, + }, + ]) + }) + .then(() => { + triggerEventHandler("change") + }) + }, + [ + displayName, + getValidateMessage, + handleUpdateMultiExecutionResult, + parseValue, + triggerEventHandler, + ], + ) + const handleOnRemove = (file: UploadItem) => { const currentFiles = previousValueRef.current.length > 0 @@ -224,7 +272,7 @@ export const UploadWidget: FC = (props) => { if (previousValueRef.current.length > 0) { previousValueRef.current = currentFiles } - triggerEventHandler("change") + handleFilesChange(currentFiles) return true } @@ -261,53 +309,12 @@ export const UploadWidget: FC = (props) => { fileListRef.current = newList previousValueRef.current = [] fileCountRef.current = 0 - triggerEventHandler("change") + handleFilesChange(newList) } } return }, - [appendFiles, getFileIndex, triggerEventHandler], - ) - - const getValidateMessage = useCallback( - (value?: UploadItem[]) => { - if (!hideValidationMessage) { - const message = handleValidateCheck({ - value, - minFiles, - maxFiles, - minSize, - maxSize, - sizeType, - required, - customRule, - }) - const showMessage = message && message.length > 0 - return showMessage ? message : "" - } - return "" - }, - [ - customRule, - hideValidationMessage, - minFiles, - maxFiles, - minSize, - sizeType, - maxSize, - required, - ], - ) - - const handleValidate = useCallback( - (value?: UploadItem[]) => { - const message = getValidateMessage(value) - handleUpdateDsl({ - validateMessage: message, - }) - return message - }, - [getValidateMessage, handleUpdateDsl], + [appendFiles, getFileIndex, handleFilesChange], ) useEffect(() => { diff --git a/apps/cloud/src/page/workspace/components/ToCloudModal/assets/discord-icon.svg b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/discord-icon.svg new file mode 100644 index 0000000000..2744a6249d --- /dev/null +++ b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/discord-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/cloud/src/page/workspace/components/ToCloudModal/assets/pricing-tip.svg b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/pricing-tip.svg new file mode 100644 index 0000000000..ec635fd4d0 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/pricing-tip.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/cloud/src/page/workspace/components/ToCloudModal/assets/upgrad-modal-bg.svg b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/upgrad-modal-bg.svg new file mode 100644 index 0000000000..f51b5c9427 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/ToCloudModal/assets/upgrad-modal-bg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/cloud/src/page/workspace/components/ToCloudModal/index.tsx b/apps/cloud/src/page/workspace/components/ToCloudModal/index.tsx new file mode 100644 index 0000000000..fc47438b94 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/ToCloudModal/index.tsx @@ -0,0 +1,140 @@ +import { FC } from "react" +import { Trans, useTranslation } from "react-i18next" +import { + Button, + CloseIcon, + DoubtIcon, + Link, + Modal, + Trigger, + TriggerProvider, +} from "@illa-design/react" +import DiscordIcon from "./assets/discord-icon.svg?react" +import TipIcon from "./assets/pricing-tip.svg?react" +import ModalDecorate from "./assets/upgrad-modal-bg.svg?react" +import { + applyCardListStyle, + boldStyle, + decorateStyle, + descriptionStyle, + doubtStyle, + footerStyle, + headerStyle, + iconStyle, + linkStyle, + modalCloseIconStyle, + modalMaskStyle, + modalStyle, + priceContentStyle, + priceStyle, + titleStyle, + upgradeButtonStyle, +} from "./style" + +interface ToCloudModalProps { + onClose: () => void +} + +export const ToCloudModal: FC = ({ onClose }) => { + const { t } = useTranslation() + + const FEATURE_CONFIG = [ + { + label: t("new_dashboard.selfhost.export.feature1"), + tip: t("new_dashboard.selfhost.export.feature_tips1"), + }, + { + label: t("new_dashboard.selfhost.export.feature2"), + tip: t("new_dashboard.selfhost.export.feature_tips2"), + }, + { + label: t("new_dashboard.selfhost.export.feature3"), + tip: t("new_dashboard.selfhost.export.feature_tips3"), + }, + { + label: t("new_dashboard.selfhost.export.feature4"), + tip: t("new_dashboard.selfhost.export.feature_tips4"), + }, + { + label: t("new_dashboard.selfhost.export.feature5"), + tip: t("new_dashboard.selfhost.export.feature_tips5"), + }, + { + label: t("new_dashboard.selfhost.export.feature6"), + tip: t("new_dashboard.selfhost.export.feature_tips6"), + }, + ] + + return ( + +
+ +
+ +
+
{t("new_dashboard.selfhost.export.title")}
+
+ + ]} + /> + +
+
+ +
+ {FEATURE_CONFIG.map(({ label, tip }) => { + return ( +
+ {label && } + {label} + + + + + +
+ ) + })} +
+
+
$16.7
+
+ {t("billing.modal.upgrade_now_admin.pricing")} +
+
+ + + +
+
+
+
+ ) +} diff --git a/apps/cloud/src/page/workspace/components/ToCloudModal/style.ts b/apps/cloud/src/page/workspace/components/ToCloudModal/style.ts new file mode 100644 index 0000000000..e468395b32 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/ToCloudModal/style.ts @@ -0,0 +1,161 @@ +import { css } from "@emotion/react" +import { applyMobileStyle } from "@illa-public/utils" +import { getColor } from "@illa-design/react" + +export const modalMaskStyle = css` + background-color: ${getColor("white", "05")}; + backdrop-filter: blur(5px); +` + +export const modalStyle = css` + border: unset; + width: 486px; + min-width: 486px; + background: ${getColor("white", "01")}; + border: 1px solid ${getColor("grayBlue", "08")}; + box-shadow: 0 4px 16px rgb(0 0 0 / 8%); + border-radius: 8px; + overflow: hidden; + + ${applyMobileStyle(css` + width: 358px; + min-width: 358px; + border-radius: 8px; + `)} +` + +export const modalCloseIconStyle = css` + position: absolute; + width: 24px; + height: 24px; + line-height: 10px; + text-align: center; + top: 18px; + right: 17px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: ${getColor("grayBlue", "02")}; +` + +export const decorateStyle = css` + width: 100%; + + ${applyMobileStyle(css` + height: 202px; + `)}; +` + +export const headerStyle = css` + padding: 16px; +` + +export const titleStyle = css` + font-weight: 600; + font-size: 18px; + line-height: 22px; + margin-bottom: 8px; + + ${applyMobileStyle(css` + font-size: 18px; + line-height: 22px; + margin-bottom: 8px; + `)}; +` + +export const descriptionStyle = css` + font-weight: 400; + font-size: 14px; + line-height: 17px; + color: ${getColor("grayBlue", "03")}; + + ${applyMobileStyle(css` + font-size: 14px; + line-height: 17px; + `)}; +` + +export const boldStyle = css` + color: ${getColor("grayBlue", "02")}; +` + +export const footerStyle = css` + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 16px; +` + +export const upgradeButtonStyle = css` + align-self: center; +` + +export const priceContentStyle = css` + font-size: 12px; + line-height: 20px; + color: ${getColor("grayBlue", "03")}; + + ${applyMobileStyle(css` + font-size: 12px; + line-height: 20px; + `)}; +` + +export const priceStyle = css` + font-weight: 500; + font-size: 14px; + line-height: 22px; + color: ${getColor("grayBlue", "02")}; +` + +export const applyCardListStyle = css` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + font-weight: 400; + font-size: 14px; + line-height: 24px; + text-align: start; + padding: 4px 24px; + + ${applyMobileStyle(css` + gap: 8px; + font-size: 14px; + line-height: 24px; + `)}; +` + +export const doubtStyle = css` + cursor: pointer; + width: 16px; + height: 16px; + display: flex; + align-items: center; + + &:hover .tips { + display: block; + } + + ${applyMobileStyle(css` + width: 16px; + height: 16px; + `)}; +` + +export const iconStyle = css` + height: 16px; + width: 16px; + flex-shrink: 0; + + ${applyMobileStyle(css` + width: 16px; + height: 16px; + `)}; +` + +export const linkStyle = css` + display: flex; + padding: 0; +` diff --git a/apps/cloud/src/page/workspace/components/UpgradeTip/index.tsx b/apps/cloud/src/page/workspace/components/UpgradeTip/index.tsx new file mode 100644 index 0000000000..ed707ba6b6 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/UpgradeTip/index.tsx @@ -0,0 +1,30 @@ +import { UpgradeIcon } from "@illa-public/icon" +import { FC } from "react" +import { useTranslation } from "react-i18next" +import { Button } from "@illa-design/react" +import { + customUpgradeIconStyle, + linkStyle, + tipsTextStyle, + upgradeTipContainerStyle, +} from "./style" +import CustomUpgradeIcon from "./upgradeIcon.svg?react" + +interface UpgradeTipProps { + openToCloudModal: () => void +} + +export const UpgradeTip: FC = ({ openToCloudModal }) => { + const { t } = useTranslation() + return ( +
+

{t("billing.homepage.get_access_to_advanc")}

+ +
+ +
+
+ ) +} diff --git a/apps/cloud/src/page/workspace/components/UpgradeTip/style.ts b/apps/cloud/src/page/workspace/components/UpgradeTip/style.ts new file mode 100644 index 0000000000..022e5c6b29 --- /dev/null +++ b/apps/cloud/src/page/workspace/components/UpgradeTip/style.ts @@ -0,0 +1,32 @@ +import { css } from "@emotion/react" + +export const upgradeTipContainerStyle = css` + width: 100%; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; + border-radius: 8px; + background-color: #f4efff; + position: relative; +` + +export const tipsTextStyle = css` + margin: 0; + font-size: 14px; + color: #403673; + font-weight: 500; + line-height: 20px; + width: 144px; +` + +export const customUpgradeIconStyle = css` + position: absolute; + top: 8px; + right: 8px; +` + +export const linkStyle = css` + line-height: 1; + font-size: 14px; +` diff --git a/apps/cloud/src/page/workspace/components/UpgradeTip/upgradeIcon.svg b/apps/cloud/src/page/workspace/components/UpgradeTip/upgradeIcon.svg new file mode 100644 index 0000000000..e9239426ad --- /dev/null +++ b/apps/cloud/src/page/workspace/components/UpgradeTip/upgradeIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/cloud/src/page/workspace/index.tsx b/apps/cloud/src/page/workspace/index.tsx index 243ae48895..643633ff5b 100644 --- a/apps/cloud/src/page/workspace/index.tsx +++ b/apps/cloud/src/page/workspace/index.tsx @@ -1,29 +1,44 @@ import { LayoutAutoChange } from "@illa-public/layout-auto-change" import { FC, useState } from "react" import ChangeLogModal from "./components/ChangeLogModal" +import { ToCloudModal } from "./components/ToCloudModal" import { MobileDashBoardLayout } from "./layout/mobile" import { PCDashBoardLayout } from "./layout/pc/" const Workspace: FC = () => { const [changeLogVisible, setChangeLogVisible] = useState(false) + const [toCloudModalVisible, setToCloudModalVisible] = useState(false) const onOpenChangeLogModal = () => { setChangeLogVisible(true) } + const openToCloudModal = () => { + setToCloudModalVisible(true) + } + return ( <> + } mobilePage={ - + } /> {changeLogVisible && ( setChangeLogVisible(false)} /> )} + {toCloudModalVisible && ( + setToCloudModalVisible(false)} /> + )} ) } diff --git a/apps/cloud/src/page/workspace/layout/interface.ts b/apps/cloud/src/page/workspace/layout/interface.ts index 42d8ce3b1d..2e04402b57 100644 --- a/apps/cloud/src/page/workspace/layout/interface.ts +++ b/apps/cloud/src/page/workspace/layout/interface.ts @@ -1,3 +1,4 @@ export interface WorkspaceLayoutProps { onOpenChangeLogModal: () => void + openToCloudModal: () => void } diff --git a/apps/cloud/src/page/workspace/layout/mobile/index.tsx b/apps/cloud/src/page/workspace/layout/mobile/index.tsx index a66718cad5..4623980094 100644 --- a/apps/cloud/src/page/workspace/layout/mobile/index.tsx +++ b/apps/cloud/src/page/workspace/layout/mobile/index.tsx @@ -7,10 +7,12 @@ import { FC, useState } from "react" import { useSelector } from "react-redux" import { Outlet } from "react-router-dom" import { DashBoardDynamicMenu } from "@/page/workspace/components/DynamicMenu" +import { UpgradeTip } from "@/page/workspace/components/UpgradeTip" import { WorkspaceLayoutProps } from "../interface" export const MobileDashBoardLayout: FC = ({ onOpenChangeLogModal, + openToCloudModal, }) => { const isLogin = useSelector(getCurrentUserID) const [drawerVisible, setDrawerVisible] = useState(false) @@ -26,6 +28,7 @@ export const MobileDashBoardLayout: FC = ({ } bottomComponent={ } diff --git a/apps/cloud/src/page/workspace/layout/pc/index.tsx b/apps/cloud/src/page/workspace/layout/pc/index.tsx index 43b126ef54..03c324b732 100644 --- a/apps/cloud/src/page/workspace/layout/pc/index.tsx +++ b/apps/cloud/src/page/workspace/layout/pc/index.tsx @@ -21,11 +21,13 @@ import { Outlet, useParams } from "react-router-dom" import { useModal } from "@illa-design/react" import { FullSectionLoading } from "@/components/FullSectionLoading" import { DashBoardDynamicMenu } from "@/page/workspace/components/DynamicMenu" +import { UpgradeTip } from "@/page/workspace/components/UpgradeTip" import { updateTutorialViewed } from "@/services/user" import { WorkspaceLayoutProps } from "../interface" export const PCDashBoardLayout: FC = ({ onOpenChangeLogModal, + openToCloudModal, }) => { const currentTeamInfo = useSelector(getCurrentTeamInfo) const isLogin = useSelector(getCurrentUserID) @@ -91,6 +93,7 @@ export const PCDashBoardLayout: FC = ({ } + tipsComponent={} bottomComponent={ } diff --git a/packages/illa-public-component b/packages/illa-public-component index a4e4f0e452..5c6baef7f5 160000 --- a/packages/illa-public-component +++ b/packages/illa-public-component @@ -1 +1 @@ -Subproject commit a4e4f0e452a3a5e6be6a915b6ec4a21c6fba6d8d +Subproject commit 5c6baef7f5bbca57fc874e9e5d9de508b701dfa5