diff --git a/api/controllers/console/workspace/workspace.py b/api/controllers/console/workspace/workspace.py index 27fb5134ee..332ed00222 100644 --- a/api/controllers/console/workspace/workspace.py +++ b/api/controllers/console/workspace/workspace.py @@ -216,6 +216,23 @@ class WebappLogoWorkspaceApi(Resource): return {"id": upload_file.id}, 201 +class WorkspaceInfoApi(Resource): + @setup_required + @login_required + @account_initialization_required + # Change workspace name + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("name", type=str, required=True, location="json") + args = parser.parse_args() + + tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404() + tenant.name = args["name"] + db.session.commit() + + return {"result": "success", "tenant": marshal(WorkspaceService.get_tenant_info(tenant), tenant_fields)} + + api.add_resource(TenantListApi, "/workspaces") # GET for getting all tenants api.add_resource(WorkspaceListApi, "/all-workspaces") # GET for getting all tenants api.add_resource(TenantApi, "/workspaces/current", endpoint="workspaces_current") # GET for getting current tenant info @@ -223,3 +240,4 @@ api.add_resource(TenantApi, "/info", endpoint="info") # Deprecated api.add_resource(SwitchWorkspaceApi, "/workspaces/switch") # POST for switching tenant api.add_resource(CustomConfigWorkspaceApi, "/workspaces/custom-config") api.add_resource(WebappLogoWorkspaceApi, "/workspaces/custom-config/webapp-logo/upload") +api.add_resource(WorkspaceInfoApi, "/workspaces/info") # POST for changing workspace info diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.module.css b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.module.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx new file mode 100644 index 0000000000..b84fd20a82 --- /dev/null +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx @@ -0,0 +1,87 @@ +'use client' +import cn from '@/utils/classnames' +import Modal from '@/app/components/base/modal' +import Input from '@/app/components/base/input' +import { useTranslation } from 'react-i18next' +import { useState } from 'react' +import { useContext } from 'use-context-selector' +import s from './index.module.css' +import Button from '@/app/components/base/button' +import { RiCloseLine } from '@remixicon/react' +import { useAppContext } from '@/context/app-context' +import { updateWorkspaceInfo } from '@/service/common' +import { ToastContext } from '@/app/components/base/toast' +type IEditWorkspaceModalProps = { + onCancel: () => void +} +const EditWorkspaceModal = ({ + onCancel, +}: IEditWorkspaceModalProps) => { + const { t } = useTranslation() + const { notify } = useContext(ToastContext) + const { currentWorkspace, isCurrentWorkspaceOwner, mutateCurrentWorkspace } = useAppContext() + const [name, setName] = useState(currentWorkspace.name) + + const changeWorkspaceInfo = async (name: string) => { + try { + await updateWorkspaceInfo({ + url: '/workspaces/info', + body: { + name, + }, + }) + notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + location.assign(`${location.origin}`) + } + catch (e) { + notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) + } + } + + return ( +
+ {}} className={cn(s.modal)}> +
+
{t('common.account.editWorkspaceInfo')}
+ +
+
+
{t('common.account.workspaceName')}
+ { + setName(e.target.value) + }} + onClear={() => { + setName(currentWorkspace.name) + }} + /> + +
+ + +
+ +
+
+
+ ) +} +export default EditWorkspaceModal diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index f7ed3f10cc..fe40d891fe 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -9,6 +9,7 @@ import { RiUserAddLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import InviteModal from './invite-modal' import InvitedModal from './invited-modal' +import EditWorkspaceModal from './edit-workspace-modal' import Operation from './operation' import { fetchMembers } from '@/service/common' import I18n from '@/context/i18n' @@ -22,6 +23,8 @@ import UpgradeBtn from '@/app/components/billing/upgrade-btn' import { NUM_INFINITE } from '@/app/components/billing/config' import { LanguagesSupported } from '@/i18n/language' import cn from '@/utils/classnames' +import Tooltip from '@/app/components/base/tooltip' +import { RiPencilLine } from '@remixicon/react' dayjs.extend(relativeTime) const MembersPage = () => { @@ -50,6 +53,7 @@ const MembersPage = () => { const { plan, enableBilling } = useProviderContext() const isNotUnlimitedMemberPlan = enableBilling && plan.type !== Plan.team && plan.type !== Plan.enterprise const isMemberFull = enableBilling && isNotUnlimitedMemberPlan && accounts.length >= plan.total.teamMembers + const [editWorkspaceModalVisible, setEditWorkspaceModalVisible] = useState(false) return ( <> @@ -59,26 +63,41 @@ const MembersPage = () => { {currentWorkspace?.name[0]?.toLocaleUpperCase()}
-
{currentWorkspace?.name}
- {enableBilling && ( -
- {isNotUnlimitedMemberPlan - ? ( -
-
{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
-
{accounts.length}
-
/
-
{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}
-
- ) - : ( -
-
{accounts.length}
-
{t('billing.plansCommon.memberAfter')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
-
- )} -
- )} +
+ {currentWorkspace?.name} + {isCurrentWorkspaceOwner && + +
{ + setEditWorkspaceModalVisible(true) + }} + > + +
+
+
} +
+
+ {enableBilling && isNotUnlimitedMemberPlan + ? ( +
+
{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
+
{accounts.length}
+
/
+
{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}
+
+ ) + : ( +
+
{accounts.length}
+
{t('billing.plansCommon.memberAfter')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}
+
+ )} +
{isMemberFull && ( @@ -145,6 +164,13 @@ const MembersPage = () => { /> ) } + { + editWorkspaceModalVisible && ( + setEditWorkspaceModalVisible(false)} + /> + ) + } ) } diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index a0f2eebc8e..fae4be5467 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -218,6 +218,9 @@ const translation = { feedbackTitle: 'Feedback', feedbackLabel: 'Tell us why you deleted your account?', feedbackPlaceholder: 'Optional', + editWorkspaceInfo: 'Edit Workspace Info', + workspaceName: 'Workspace Name', + workspaceIcon: 'Workspace Icon', }, members: { team: 'Team', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 3d40f2ab5d..8a47801f58 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -218,6 +218,9 @@ const translation = { feedbackLabel: 'アカウントを削除した理由を教えてください。', feedbackPlaceholder: '随意', sendVerificationButton: '確認コードの送信', + editWorkspaceInfo: 'ワークスペース情報を編集', + workspaceName: 'ワークスペース名', + workspaceIcon: 'ワークスペースアイコン', }, members: { team: 'チーム', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index ef865263bc..18933b0759 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -218,6 +218,9 @@ const translation = { feedbackTitle: '反馈', feedbackLabel: '请告诉我们您为什么删除账户?', feedbackPlaceholder: '选填', + editWorkspaceInfo: '编辑工作空间信息', + workspaceName: '工作空间名称', + workspaceIcon: '工作空间图标', }, members: { team: '团队', diff --git a/web/service/common.ts b/web/service/common.ts index a978ce1a49..9e0ea4aef1 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -148,6 +148,10 @@ export const switchWorkspace: Fetcher(url, { body }) } +export const updateWorkspaceInfo: Fetcher }> = ({ url, body }) => { + return post(url, { body }) +} + export const fetchDataSource: Fetcher<{ data: DataSourceNotion[] }, { url: string }> = ({ url }) => { return get<{ data: DataSourceNotion[] }>(url) }