diff --git a/web/src/beta/features/AccountSetting/index.tsx b/web/src/beta/features/AccountSetting/index.tsx
index 3cd239cc58..18c6ab1980 100644
--- a/web/src/beta/features/AccountSetting/index.tsx
+++ b/web/src/beta/features/AccountSetting/index.tsx
@@ -50,65 +50,67 @@ const AccountSetting: FC = () => {
];
return (
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
-
- {t("Password")}
-
-
- {
- setChangePasswordModal(true);
- }}
- size="medium"
- hasBorder={true}
- />
-
-
- {
- handleUpdateUserLanguage({ lang: value as string });
- }}
- />
-
-
-
+
+ {t("Password")}
+
+
+ {
+ setChangePasswordModal(true);
+ }}
+ size="medium"
+ hasBorder={true}
+ />
+
+
+ {
+ handleUpdateUserLanguage({ lang: value as string });
+ }}
+ />
+
+
+
- setChangePasswordModal(false)}
- handleUpdateUserPassword={handleUpdateUserPassword}
- />
-
+ setChangePasswordModal(false)}
+ handleUpdateUserPassword={handleUpdateUserPassword}
+ />
+
+
-
+ >
);
};
export default AccountSetting;
diff --git a/web/src/beta/features/Dashboard/ContentsContainer/Members/ListItem.tsx b/web/src/beta/features/Dashboard/ContentsContainer/Members/ListItem.tsx
index d273d5c5de..afd6ec981c 100644
--- a/web/src/beta/features/Dashboard/ContentsContainer/Members/ListItem.tsx
+++ b/web/src/beta/features/Dashboard/ContentsContainer/Members/ListItem.tsx
@@ -1,10 +1,9 @@
import { Typography } from "@reearth/beta/lib/reearth-ui";
+import { TeamMember } from "@reearth/services/gql";
import { styled } from "@reearth/services/theme";
import { FC } from "react";
-import { Member } from "../../type";
-
-const ListItem: FC<{ member: Member }> = ({ member }) => {
+const ListItem: FC<{ member: TeamMember }> = ({ member }) => {
return (
diff --git a/web/src/beta/features/Dashboard/type.ts b/web/src/beta/features/Dashboard/type.ts
index f90f19dcc0..9eff64c7b8 100644
--- a/web/src/beta/features/Dashboard/type.ts
+++ b/web/src/beta/features/Dashboard/type.ts
@@ -1,4 +1,5 @@
import { IconName } from "@reearth/beta/lib/reearth-ui";
+import { TeamMember } from "@reearth/services/gql";
import { ProjectType } from "@reearth/types";
export type Project = {
@@ -47,7 +48,7 @@ export type Member = {
export type Workspace = {
id: string;
name: string;
- members?: Member[];
+ members?: TeamMember[];
policyId?: string | null;
policy?: { id: string; name: string } | null;
personal?: boolean;
diff --git a/web/src/beta/features/WorkspaceSetting/hooks.ts b/web/src/beta/features/WorkspaceSetting/hooks.ts
index b0400e3b99..b2a6f872c6 100644
--- a/web/src/beta/features/WorkspaceSetting/hooks.ts
+++ b/web/src/beta/features/WorkspaceSetting/hooks.ts
@@ -1,9 +1,9 @@
-import { useWorkspaceFetcher } from "@reearth/services/api";
+import { useMeFetcher, useWorkspaceFetcher } from "@reearth/services/api";
import { Role } from "@reearth/services/gql";
import { useCallback } from "react";
export type WorkspacePayload = {
- name: string;
+ name?: string;
userId?: string;
teamId: string;
role?: Role;
@@ -24,16 +24,14 @@ export default () => {
useUpdateWorkspace,
useDeleteWorkspace,
useAddMemberToWorkspace,
- useRemoveMemberFromWorkspace
+ useRemoveMemberFromWorkspace,
+ useUpdateMemberOfWorkspace
} = useWorkspaceFetcher();
// Fetch a specific workspace
const handleFetchWorkspace = useCallback(
(workspaceId: string) => {
const { workspace, loading, error } = useWorkspaceQuery(workspaceId);
- if (error) {
- console.error("Failed to fetch workspace:", error);
- }
return { workspace, loading, error };
},
[useWorkspaceQuery]
@@ -42,22 +40,14 @@ export default () => {
// Fetch all workspaces
const handleFetchWorkspaces = useCallback(() => {
const { workspaces, loading, error } = useWorkspacesQuery();
- if (error) {
- console.error("Failed to fetch workspaces:", error);
- }
return { workspaces, loading, error };
}, [useWorkspacesQuery]);
// Create a new workspace
const handleCreateWorkspace = useCallback(
async ({ name }: WorkspacePayload) => {
- try {
- const { status } = await useCreateWorkspace(name);
- if (status === "success") {
- console.log("Workspace created successfully");
- }
- } catch (error) {
- console.error("Failed to create workspace:", error);
+ if (name) {
+ await useCreateWorkspace(name);
}
},
[useCreateWorkspace]
@@ -66,13 +56,8 @@ export default () => {
// Update an existing workspace
const handleUpdateWorkspace = useCallback(
async ({ teamId, name }: WorkspacePayload) => {
- try {
- const { status } = await useUpdateWorkspace(teamId, name);
- if (status === "success") {
- console.log("Workspace updated successfully");
- }
- } catch (error) {
- console.error("Failed to update workspace:", error);
+ if (name && teamId) {
+ await useUpdateWorkspace(teamId, name);
}
},
[useUpdateWorkspace]
@@ -81,13 +66,8 @@ export default () => {
// Delete a workspace
const handleDeleteWorkspace = useCallback(
async (teamId: string) => {
- try {
- const { status } = await useDeleteWorkspace(teamId);
- if (status === "success") {
- console.log("Workspace deleted successfully");
- }
- } catch (error) {
- console.error("Failed to delete workspace:", error);
+ if (teamId) {
+ await useDeleteWorkspace(teamId);
}
},
[useDeleteWorkspace]
@@ -96,19 +76,8 @@ export default () => {
// Add a member to a workspace
const handleAddMemberToWorkspace = useCallback(
async ({ teamId, userId, role }: WorkspacePayload) => {
- try {
- if (userId && role) {
- const { status } = await useAddMemberToWorkspace(
- teamId,
- userId,
- role
- );
- if (status === "success") {
- console.log("Member added successfully");
- }
- }
- } catch (error) {
- console.error("Failed to add member to workspace:", error);
+ if (userId && role) {
+ await useAddMemberToWorkspace(teamId, userId, role);
}
},
[useAddMemberToWorkspace]
@@ -117,20 +86,35 @@ export default () => {
// Remove a member from a workspace
const handleRemoveMemberFromWorkspace = useCallback(
async ({ teamId, userId }: WorkspacePayload) => {
- try {
- if (userId) {
- const { status } = await useRemoveMemberFromWorkspace(teamId, userId);
- if (status === "success") {
- console.log("Member removed successfully");
- }
- }
- } catch (error) {
- console.error("Failed to remove member from workspace:", error);
+ if (userId) {
+ await useRemoveMemberFromWorkspace(teamId, userId);
}
},
[useRemoveMemberFromWorkspace]
);
+ // update a member of workspace
+ const handleUpdateMemberOfWorkspace = useCallback(
+ async ({ teamId, userId, role }: WorkspacePayload) => {
+ if (userId && role) {
+ await useUpdateMemberOfWorkspace(teamId, userId, role);
+ }
+ },
+ [useUpdateMemberOfWorkspace]
+ );
+
+ const { useSearchUser } = useMeFetcher();
+ const handleSearchUser = useCallback(
+ (nameOrEmail: string) => {
+ const { user, status } = useSearchUser(nameOrEmail, {
+ skip: !nameOrEmail
+ });
+
+ return { searchUser: user, searchUserStatus: status };
+ },
+ [useSearchUser]
+ );
+
return {
handleFetchWorkspace,
handleFetchWorkspaces,
@@ -138,6 +122,8 @@ export default () => {
handleUpdateWorkspace,
handleDeleteWorkspace,
handleAddMemberToWorkspace,
- handleRemoveMemberFromWorkspace
+ handleRemoveMemberFromWorkspace,
+ handleUpdateMemberOfWorkspace,
+ handleSearchUser
};
};
diff --git a/web/src/beta/features/WorkspaceSetting/index.tsx b/web/src/beta/features/WorkspaceSetting/index.tsx
index cb7b11cffe..90853f5937 100644
--- a/web/src/beta/features/WorkspaceSetting/index.tsx
+++ b/web/src/beta/features/WorkspaceSetting/index.tsx
@@ -6,6 +6,7 @@ import CursorStatus from "../CursorStatus";
import useProjectsHook from "../Dashboard/ContentsContainer/Projects/hooks";
import useWorkspaceHook from "./hooks";
+import Members from "./innerPages/Members/Members";
import Workspace from "./innerPages/Workspaces/Workspaces";
type Props = {
@@ -13,11 +14,20 @@ type Props = {
workspaceId?: string;
};
+enum TABS {
+ WORKSPACE = "workspace",
+ MEMBERS = "members"
+}
+
const WorkspaceSetting: FC = ({ tab, workspaceId }) => {
const {
handleFetchWorkspaces,
handleUpdateWorkspace,
- handleDeleteWorkspace
+ handleDeleteWorkspace,
+ handleAddMemberToWorkspace,
+ handleSearchUser,
+ handleUpdateMemberOfWorkspace,
+ handleRemoveMemberFromWorkspace
} = useWorkspaceHook();
const { filtedProjects } = useProjectsHook(workspaceId);
@@ -27,7 +37,7 @@ const WorkspaceSetting: FC = ({ tab, workspaceId }) => {
return (
<>
- {tab === "workspace" && (
+ {tab === TABS.WORKSPACE && (
= ({ tab, workspaceId }) => {
projectsCount={filtedProjects?.length}
/>
)}
+ {tab === TABS.MEMBERS && (
+
+ )}
>
diff --git a/web/src/beta/features/WorkspaceSetting/innerPages/Members/Members.tsx b/web/src/beta/features/WorkspaceSetting/innerPages/Members/Members.tsx
new file mode 100644
index 0000000000..d92a3f58ad
--- /dev/null
+++ b/web/src/beta/features/WorkspaceSetting/innerPages/Members/Members.tsx
@@ -0,0 +1,472 @@
+import {
+ ButtonWrapper,
+ InnerPage,
+ SettingsWrapper
+} from "@reearth/beta/features/ProjectSettings/innerPages/common";
+import {
+ Collapse,
+ Button,
+ Modal,
+ Typography,
+ ModalPanel,
+ TextInput,
+ IconButton,
+ Icon,
+ PopupMenu
+} from "@reearth/beta/lib/reearth-ui";
+import { SelectField } from "@reearth/beta/ui/fields";
+import { metricsSizes } from "@reearth/beta/utils/metrics";
+import { Role } from "@reearth/services/gql";
+import { useT } from "@reearth/services/i18n";
+import { useWorkspace } from "@reearth/services/state";
+import { styled, useTheme, keyframes } from "@reearth/services/theme";
+import { FC, KeyboardEvent, useEffect, useState } from "react";
+import { Fragment } from "react/jsx-runtime";
+
+import { WorkspacePayload } from "../../hooks";
+
+type Props = {
+ handleAddMemberToWorkspace: ({
+ teamId,
+ userId,
+ role
+ }: WorkspacePayload) => Promise;
+ handleSearchUser: (nameOrEmail: string) =>
+ | {
+ searchUser: {
+ __typename?: "User";
+ id: string;
+ name: string;
+ email: string;
+ } | null;
+ searchUserStatus: string;
+ error?: undefined;
+ }
+ | {
+ error: unknown;
+ searchUser?: undefined;
+ searchUserStatus?: undefined;
+ };
+ handleUpdateMemberOfWorkspace: ({
+ teamId,
+ userId,
+ role
+ }: WorkspacePayload) => Promise;
+ handleRemoveMemberFromWorkspace: ({
+ teamId,
+ userId
+ }: WorkspacePayload) => Promise;
+};
+
+type MemberData = {
+ id: string;
+ role: Role;
+ username?: string;
+ email?: string;
+};
+
+type MembersData = MemberData[];
+
+type MemberSearchResult = {
+ userName: string;
+ email: string;
+ id: string;
+};
+
+const Members: FC = ({
+ handleSearchUser,
+ handleAddMemberToWorkspace,
+ handleUpdateMemberOfWorkspace,
+ handleRemoveMemberFromWorkspace
+}) => {
+ const theme = useTheme();
+ const t = useT();
+ const roles = [
+ { value: "READER", label: t("Reader") },
+ { value: "WRITER", label: t("Writer") },
+ { value: "MAINTAINER", label: t("Maintainer") },
+ { value: "OWNER", label: t("Owner") }
+ ];
+
+ const [currentWorkspace] = useWorkspace();
+ const [workspaceMembers, setWorkspaceMembers] = useState([]);
+
+ useEffect(() => {
+ setWorkspaceMembers(
+ currentWorkspace?.members
+ ?.filter((m) => !!m.user)
+ .map((member) => ({
+ id: member.userId,
+ role: member.role,
+ username: member.user?.name,
+ email: member.user?.email
+ })) ?? []
+ );
+ }, [currentWorkspace]);
+
+ const [addMemberModal, setAddMemberModal] = useState(false);
+ const [activeEditIndex, setActiveEditIndex] = useState(null);
+
+ const [memberSearchInput, setMemberSearchInput] = useState("");
+ const [debouncedInput, setDebouncedInput] =
+ useState(memberSearchInput);
+ const [memberSearchResults, setMemberSearchResults] = useState<
+ MemberSearchResult[]
+ >([]);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedInput(memberSearchInput.trim());
+ }, 1000);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [memberSearchInput]);
+
+ const { searchUser, searchUserStatus } = handleSearchUser(debouncedInput);
+
+ useEffect(() => {
+ if (
+ searchUser &&
+ "name" in searchUser &&
+ !memberSearchResults.find(
+ (memberSearchResult) => memberSearchResult.id === searchUser.id
+ )
+ ) {
+ setMemberSearchResults([
+ ...memberSearchResults,
+ {
+ userName: searchUser.name,
+ email: searchUser.email,
+ id: searchUser.id
+ }
+ ]);
+ }
+ }, [memberSearchResults, searchUser]);
+
+ const handleNewMemberClick = () => {
+ setAddMemberModal(true);
+ };
+
+ const handleCloseAddMemberModal = () => {
+ setAddMemberModal(false);
+ };
+
+ const handleChangeRoleButtonClick = (index: number) => {
+ setActiveEditIndex((prevIndex) => (prevIndex === index ? null : index));
+ };
+
+ const handleChangeRole = async (
+ user: MemberData,
+ index: number,
+ roleValue: string | string[]
+ ) => {
+ if (currentWorkspace?.id) {
+ await handleUpdateMemberOfWorkspace({
+ teamId: currentWorkspace?.id,
+ userId: user.id,
+ role: roleValue as Role
+ });
+ setWorkspaceMembers((prevMembers) => {
+ return prevMembers.map((workspaceMember) =>
+ workspaceMember.id === user.id
+ ? {
+ ...workspaceMember,
+ role: roleValue as Role
+ }
+ : workspaceMember
+ );
+ });
+ setActiveEditIndex((prevIndex) => (prevIndex === index ? null : index));
+ }
+ };
+
+ const handleRemoveMemberButtonClick = (userId: string) => {
+ if (currentWorkspace?.id) {
+ handleRemoveMemberFromWorkspace({
+ teamId: currentWorkspace?.id,
+ userId
+ });
+ setWorkspaceMembers(
+ workspaceMembers.filter(
+ (workspaceMember) => workspaceMember.id !== userId
+ )
+ );
+ }
+ };
+
+ const handleAddMember = () => {
+ memberSearchResults.forEach((memberSearchResult) => {
+ if (currentWorkspace?.id) {
+ handleAddMemberToWorkspace({
+ name: memberSearchResult.userName,
+ teamId: currentWorkspace?.id,
+ userId: memberSearchResult.id,
+ role: Role.Reader
+ });
+ setWorkspaceMembers((prevMembers) => [
+ ...prevMembers,
+ {
+ username: memberSearchResult.userName,
+ email: memberSearchResult.email,
+ role: Role.Reader,
+ id: memberSearchResult.id
+ }
+ ]);
+ setAddMemberModal(false);
+ setMemberSearchInput("");
+ setDebouncedInput("");
+ setMemberSearchResults([]);
+ }
+ });
+ };
+
+ const handleDeleteUserForSearchResult = (
+ memberSearchResult: MemberSearchResult
+ ) => {
+ setMemberSearchInput("");
+ setDebouncedInput("");
+ setMemberSearchResults(
+ memberSearchResults.filter(
+ (element) => element.id !== memberSearchResult.id
+ )
+ );
+ };
+
+ const handleUserSearchInputOnKeyDown = (
+ e: KeyboardEvent
+ ) => {
+ if (e.key === "Enter" && memberSearchInput.trim() !== "") {
+ setMemberSearchInput("");
+ setDebouncedInput(memberSearchInput.trim());
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {t("User Name")}
+ {t("Email")}
+ {t("Role")}
+
+
+ {workspaceMembers?.map((user, index) => (
+
+ {user.username}
+ {user.email}
+ {activeEditIndex !== index ? (
+ {user.role}
+ ) : (
+
+ {
+ await handleChangeRole(user, index, roleValue);
+ }}
+ />
+
+ )}
+
+
+ }
+ menu={[
+ {
+ icon: "arrowLeftRight",
+ id: "changeRole",
+ title: t("Change Role"),
+ disabled: user.role === "OWNER",
+ onClick: () => handleChangeRoleButtonClick(index)
+ },
+ {
+ icon: "close",
+ id: "remove",
+ title: t("Remove"),
+ disabled: user.role === "OWNER",
+ onClick: () => handleRemoveMemberButtonClick(user.id)
+ }
+ ]}
+ />
+
+
+ ))}
+
+
+
+
+
+
+ ,
+
+
+ );
+};
+
+const zoomIn = keyframes`
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+`;
+
+const SettingsFields = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ justifyContent: "flex-end",
+ gap: theme.spacing.largest
+}));
+
+const Table = styled.div`
+ display: grid;
+ grid-template-columns: 5fr 2.5fr 2fr 1fr;
+ gap: 16px;
+ padding: 10px;
+ color: ${({ theme }) => theme.content.main};
+`;
+
+const TableHeader = styled("div")(({ theme }) => ({
+ fontSize: theme.fonts.sizes.body,
+ color: theme.content.weak,
+ lineHeight: "28px",
+ display: "flex",
+ alignItems: "center"
+}));
+
+const TableRow = styled("div")(({ theme }) => ({
+ color: theme.content.main,
+ fontSize: theme.fonts.sizes.body,
+ lineHeight: "28px",
+ display: "flex",
+ alignItems: "center",
+ animation: `${zoomIn} 0.2s ease-in-out`
+}));
+
+const ModalContentWrapper = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: theme.spacing.large,
+ padding: theme.spacing.large,
+ background: theme.bg[1],
+ borderRadius: theme.radius.large
+}));
+
+const ItemContainer = styled("div")(({ theme }) => ({
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "0px 12px",
+ fontSize: theme.fonts.sizes.body
+}));
+
+const UserInfo = styled.div`
+ display: flex;
+ flex-direction: column;
+ animation: ${zoomIn} 0.2s ease-in-out;
+`;
+
+const DeleteIcon = styled(IconButton)(({ theme }) => ({
+ cursor: "pointer",
+ color: theme.content.main,
+ "&:hover": {
+ color: theme.dangerous.main
+ }
+}));
+
+const SearchMemberMessage = styled(Typography)`
+ margin-top: ${metricsSizes["2xs"]}px;
+ display: flex;
+`;
+
+export default Members;
diff --git a/web/src/beta/hooks/useAccountSettingsTabs.ts b/web/src/beta/hooks/useAccountSettingsTabs.ts
index 7b4e02064a..5e949d7a4e 100644
--- a/web/src/beta/hooks/useAccountSettingsTabs.ts
+++ b/web/src/beta/hooks/useAccountSettingsTabs.ts
@@ -20,8 +20,7 @@ export const accountSettingTabs: {
id: "members",
text: "Members",
icon: "users",
- path: "/settings/workspaces/:workspaceId/members",
- disabled: true
+ path: "/settings/workspaces/:workspaceId/members"
}
] as const;
diff --git a/web/src/beta/lib/reearth-ui/components/Icon/Icons/MemberAdd.svg b/web/src/beta/lib/reearth-ui/components/Icon/Icons/MemberAdd.svg
new file mode 100644
index 0000000000..0e610bfe90
--- /dev/null
+++ b/web/src/beta/lib/reearth-ui/components/Icon/Icons/MemberAdd.svg
@@ -0,0 +1,7 @@
+
diff --git a/web/src/beta/lib/reearth-ui/components/Icon/icons.ts b/web/src/beta/lib/reearth-ui/components/Icon/icons.ts
index 7a47a04bc9..a2ae4e9e3c 100644
--- a/web/src/beta/lib/reearth-ui/components/Icon/icons.ts
+++ b/web/src/beta/lib/reearth-ui/components/Icon/icons.ts
@@ -88,6 +88,7 @@ import LToS from "./Icons/LToS.svg?react";
import MagnifyingGlass from "./Icons/MagnifyingGlass.svg?react";
import MapPin from "./Icons/MapPin.svg?react";
import MapTrifold from "./Icons/MapTrifold.svg?react";
+import MemberAdd from "./Icons/MemberAdd.svg?react";
import Minus from "./Icons/Minus.svg?react";
import More from "./Icons/More.svg?react";
import NavigatorAngle from "./Icons/NavigatorAngle.svg?react";
@@ -246,6 +247,7 @@ export default {
magnifyingGlass: MagnifyingGlass,
mapPin: MapPin,
mapTrifold: MapTrifold,
+ memberAdd: MemberAdd,
minus: Minus,
more: More,
navigatorAngle: NavigatorAngle,
diff --git a/web/src/services/api/meApi.ts b/web/src/services/api/meApi.ts
index 4327b60af9..92e6fa1df1 100644
--- a/web/src/services/api/meApi.ts
+++ b/web/src/services/api/meApi.ts
@@ -2,7 +2,8 @@ import { useMutation, useQuery } from "@apollo/client";
import {
GET_ME,
DELETE_ME,
- UPDATE_ME
+ UPDATE_ME,
+ GET_USER_BY_SEARCH
} from "@reearth/services/gql/queries/user";
import { useCallback } from "react";
@@ -77,6 +78,31 @@ export default () => {
[deleteMeMutation, setNotification, t]
);
+ const useSearchUser = useCallback(
+ (nameOrEmail: string, options?: { skip?: boolean }) => {
+ const { data, loading, error } = useQuery(GET_USER_BY_SEARCH, {
+ variables: { nameOrEmail },
+ skip: options?.skip
+ });
+
+ if (error) {
+ console.log("GraphQL: Failed to search user", error);
+ setNotification({
+ type: "error",
+ text: t("Failed to search user.")
+ });
+ return { status: "error", user: null };
+ }
+
+ if (!loading && data?.searchUser) {
+ return { status: "success", user: data.searchUser };
+ }
+
+ return { status: loading ? "loading" : "idle", user: null };
+ },
+ [setNotification, t]
+ );
+
const useUpdateLanguage = useCallback(
async (lang: string) => {
if (!lang) return;
@@ -103,6 +129,7 @@ export default () => {
useMeQuery,
useUpdatePassword,
useDeleteUser,
+ useSearchUser,
useUpdateLanguage
};
};
diff --git a/web/src/services/i18n/translations/en.yml b/web/src/services/i18n/translations/en.yml
index 4d5a1de431..4870df8ed1 100644
--- a/web/src/services/i18n/translations/en.yml
+++ b/web/src/services/i18n/translations/en.yml
@@ -282,6 +282,20 @@ Spacing settings: ''
Remove: ''
Block: ''
Unsupported field type: ''
+Reader: ''
+Writer: ''
+Maintainer: ''
+Owner: ''
+Members: ''
+New member: ''
+User Name: ''
+Email: ''
+Role: ''
+Change Role: ''
+Add a team member: ''
+Add: ''
+Email address or a user name: ''
+Didn’t find the user: ''
Workspace: ''
Workspace Name: ''
Remove this workspace: ''
@@ -381,6 +395,7 @@ Failed to update user password.: ''
Successfully updated user password!: ''
Failed to delete user.: ''
Successfully delete user!: ''
+Failed to search user.: ''
Failed to change language.: ''
Successfully updated user language!: ''
Failed to install plugin.: ''
@@ -447,4 +462,3 @@ Failed to remove member from workspace.: ''
Successfully removed member from workspace!: ''
Failed to update member in workspace.: ''
Successfully updated member in workspace!: ''
-Style_: ''
diff --git a/web/src/services/i18n/translations/ja.yml b/web/src/services/i18n/translations/ja.yml
index 5fb9f7cfe9..66c24e5cd1 100644
--- a/web/src/services/i18n/translations/ja.yml
+++ b/web/src/services/i18n/translations/ja.yml
@@ -1,8 +1,8 @@
Auto: ''
Account: ''
-Name: 名前
+Name: ''
Email address: ''
-Password: パスワード
+Password: ''
Language: ''
No whitespace is allowed.: ''
Too short.: ''
@@ -17,12 +17,12 @@ New password: ''
New password (for confirmation): ''
'"repeatPassword" Passwords need to match': ''
Select Asset: ''
-Last Uploaded: 昇順(アップロード日)
-First Uploaded: 降順(アップロード日)
-A To Z: 昇順(ファイル名)
-Z To A: 降順(ファイル名)
-Size Small to Large: 昇順(サイズ)
-Size Large to Small: 降順(サイズ)
+Last Uploaded: ''
+First Uploaded: ''
+A To Z: ''
+Z To A: ''
+Size Small to Large: ''
+Size Large to Small: ''
Upload File: ''
Search in all assets library: ''
Assets selected: ''
@@ -36,11 +36,11 @@ No Member match your search.: ''
Last Created: ''
First Created: ''
Last Updated: ''
-New Project: 新規プロジェクト
-Import: インポート
+New Project: ''
+Import: ''
All projects: ''
Search Result for: ''
-Project Name: プロジェクト名
+Project Name: ''
Updated At: ''
Created At: ''
No Project has been created yet: ''
@@ -51,7 +51,7 @@ Move to Recycle Bin: ''
Create new project: ''
Project Name *: ''
Text: ''
-Description: プロジェクト概要
+Description: ''
Write down your content: ''
Cover Image: ''
Your project will be moved to Recycle Bin.: ''
@@ -72,59 +72,59 @@ Workspace Settings: ''
Account Settings: ''
Log Out: ''
Re:Earth Visualizer: ''
-Page: ページ
-From Assets: アセットから追加
-From Web: Webから追加
-From Value: 値を入力
-File Format: ファイル形式
-File format of the data source you want to add.: 追加したいデータソースのファイル形式を選択
-Source Type: ソースの種類
-Asset: アセット
-Resource URL: リソースURL
-Input Text: 入力
-Value: 値
-Input data here: データ入力
-Prioritize Performance: パフォーマンスを優先
-Add to Layer: レイヤー追加
+Page: ''
+From Assets: ''
+From Web: ''
+From Value: ''
+File Format: ''
+File format of the data source you want to add.: ''
+Source Type: ''
+Asset: ''
+Resource URL: ''
+Input Text: ''
+Value: ''
+Input data here: ''
+Prioritize Performance: ''
+Add to Layer: ''
Visualizer currently only supports CSV point data. Please specify the column names for latitude and longitude in your data below.: ''
Latitude Column Name: ''
Column Name: ''
Longitude Column Name: ''
-Common: Common
-CSV: CSV
-WMS: WMS
-Vector Tile: Vector Tile
-3D Tiles: 3D Tiles
-Data Source Manager: データソースマネージャー
+Common: ''
+CSV: ''
+WMS: ''
+Vector Tile: ''
+3D Tiles: ''
+Data Source Manager: ''
'Google Maps API Key ': ''
'You can apply a key ': ''
here: ''
Cesium OSM 3D Tiles: ''
Google Photorealistic 3D Tiles: ''
URL: ''
-Choose layer to add: レイヤーの選択
-layer name: レイヤー名
+Choose layer to add: ''
+layer name: ''
Layer name: ''
-Inspector: インスペクター
+Inspector: ''
Layer Name: ''
-Format: フォーマット
+Format: ''
Unsupported custom field: ''
Custom Properties: ''
Save & Apply: ''
No custom properties: ''
-Geometry: ジオメトリ
-Properties: プロパティ
-Enable Infobox: インフォボックスを有効化
-Show infobox when the user clicks on a layer: フェーチャーをクリックした際にインフォボックスを表示
-Layer Style: レイヤースタイル
-Settings: 設定
-Add Layer from Resource: リソースからレイヤーを追加
-Add Sketch Layer: スケッチレイヤーを追加
-Layers: レイヤー
+Geometry: ''
+Properties: ''
+Enable Infobox: ''
+Show infobox when the user clicks on a layer: ''
+Layer Style: ''
+Settings: ''
+Add Layer from Resource: ''
+Add Sketch Layer: ''
+Layers: ''
Interface: ''
Code: ''
Invalid style: ''
-Save: 保存
+Save: ''
No style selected: ''
if: ''
Condition is incompatible with the current system for this node or value.: ''
@@ -137,7 +137,7 @@ italic: ''
New node: ''
Style: ''
Empty: ''
-Default: デフォルト
+Default: ''
Professional: ''
Basic Geometry: ''
Points: ''
@@ -148,17 +148,17 @@ Extruded polygon: ''
Simple Style: ''
Plateau: ''
Color buildings by height: ''
-Main: メイン
-Tiles: タイル
-Terrain: 地形
-Globe: 地球
-Sky: 空
-Camera: カメラ
-Unknown scene setting: 不明なシーン設定
-Scene: シーン
+Main: ''
+Tiles: ''
+Terrain: ''
+Globe: ''
+Sky: ''
+Camera: ''
+Unknown scene setting: ''
+Scene: ''
Type Title here: ''
Please select one type: ''
-Title: ページタイトル
+Title: ''
Type: ''
The keyword you want to use as the custom property title has been used in the system, please choose any other keyword: ''
New Property: ''
@@ -213,12 +213,12 @@ Widget Manager: ウィジェット管理
Project settings: プロジェクト設定
Plugin: プラグイン
Visualizer: ''
-Unknown: 不明
+Unknown: ''
Switch workspace: ''
-Log out: ログアウト
+Log out: ''
Asset management: ''
-Map: マップ
-Widgets: ウィジェット
+Map: ''
+Widgets: ''
Oops, This Page Not Found!: ''
Go to Dashboard: ''
Error: エラー
@@ -245,43 +245,57 @@ Install: インストール
You are uninstalling the selected plugin. The data used by this plugin may also be deleted.: 選択中のプラグインをアンインストールします。このプラグインに紐付いたデータも削除されます。よろしいですか?
Please be sure before uninstalling.: プラグインをアンインストールする前にご確認ください。
No description.: ''
-Import GitHub repository: GitHubレポジトリからインポート
-Continue: 続ける
-'Repository url:': 'レポジトリURL:'
-Public Info: 公開詳細設定
-Basic Authorization: ベーシック認証
-Enable Basic Authorization: ベーシック認証を有効化
-Username: ユーザー名
-Site Setting: サイト設定
-Site name: サイト名
-You are about to change the site name for your project. Only alphanumeric characters and hyphens are allows.: プロジェクト公開ページのサイト名を変更します。アルファベットとハイフン(-)のみ利用可能です。
+Import GitHub repository: ''
+Continue: ''
+'Repository url:': ''
+Public Info: ''
+Basic Authorization: ''
+Enable Basic Authorization: ''
+Username: ''
+Site Setting: ''
+Site name: ''
+You are about to change the site name for your project. Only alphanumeric characters and hyphens are allows.: ''
Google Analytics: ''
-Enable Google Analytics: 有効
+Enable Google Analytics: ''
Tracking ID: ''
Custom Domain: ''
-Left: 左
-Right: 右
-Story Panel: ストーリーパネル
-Panel Position: パネルポジション
-Background Color: 背景色
-Your account has been successfully verified! Feel free to login now.: メールアドレスが認証されました。ログインが可能です。
-Could not verify your signup. Please start the process over.: メールアドレスが認証できませんでした。もう一度最初からやり直してください。
-New Field: 新しいフィールド
-New Camera: 新しいカメラ
-New Camera Button: 新しいカメラボタン
-Buttons List: ボタン一覧
-New Layers Button: 新しいレイヤーボタン
+Left: ''
+Right: ''
+Story Panel: ''
+Panel Position: ''
+Background Color: ''
+Your account has been successfully verified! Feel free to login now.: ''
+Could not verify your signup. Please start the process over.: ''
+New Field: ''
+New Camera: ''
+New Camera Button: ''
+Buttons List: ''
+New Layers Button: ''
New Link Button: ''
-Add markdown text here: マークダウンテキストを入力
-aria-label-compass: コンパス
-aria-label-adjust-angle: 角度を調節する
-aria-label-zoom-in: ズームイン
-aria-label-Go-to-the-home-position: 元の位置に戻る
-aria-label-zoom-out: ズームアウト
-Spacing settings: 余白設定
-Remove: 削除
-Block: ブロック
-Unsupported field type: サポートされていないフィールドタイプです
+Add markdown text here: ''
+aria-label-compass: ''
+aria-label-adjust-angle: ''
+aria-label-zoom-in: ''
+aria-label-Go-to-the-home-position: ''
+aria-label-zoom-out: ''
+Spacing settings: ''
+Remove: ''
+Block: ''
+Unsupported field type: ''
+Reader: ''
+Writer: ''
+Maintainer: ''
+Owner: ''
+Members: ''
+New member: ''
+User Name: ''
+Email: ''
+Role: ''
+Change Role: ''
+Add a team member: ''
+Add: ''
+Email address or a user name: ''
+Didn’t find the user: ''
Workspace: ''
Workspace Name: ''
Remove this workspace: ''
@@ -294,93 +308,94 @@ This will permanently delete the workspace and all related projects, assets and
Please type your workspace name to continue.: ''
You are going to delete a workspace.: ''
Please to make sure you don’t have any projects in your workspace, then you can continue.: ''
-Write your story :): ストーリーを書きましょう!
-Normal: 標準テキスト
-Heading 1: 見出し1
-Heading 2: 見出し2
-Heading 3: 見出し3
-Bullet List: 箇条書き
-Numbered List: 番号付リスト
-Quote: 引用
-Formatting options for font family: フォントの書式設定
-Formatting options for font size: フォントサイズの書式設定
-Formatting options for line height: 行間隔の書式設定
-Center Align: 中央揃え
-Justify Align: 両端揃え
-Left Align: 左揃え
-Right Align: 右揃え
-Outdent: インデント(減)
-Indent: インデント(増)
-Bulleted List: 箇条書き
-Insert Code Block: コードブロック組み込み
-Insert Link: リンク組み込み
-Text Color: テキスト色
-Strikethrough: 取り消し線
-Subscript: 下付き文字
-Superscript: 上付き文字
-Clear Formatting: 書式をクリア
+Write your story :): ''
+Normal: ''
+Heading 1: ''
+Heading 2: ''
+Heading 3: ''
+Bullet List: ''
+Numbered List: ''
+Quote: ''
+Formatting options for font family: ''
+Formatting options for font size: ''
+Formatting options for line height: ''
+Center Align: ''
+Justify Align: ''
+Left Align: ''
+Right Align: ''
+Outdent: ''
+Indent: ''
+Bulleted List: ''
+Insert Code Block: ''
+Insert Link: ''
+Text Color: ''
+Strikethrough: ''
+Subscript: ''
+Superscript: ''
+Clear Formatting: ''
'Sort:': ''
-Wrong file format: ファイルのフォーマットが異なります
-Not set: 未設定
+Wrong file format: ''
+Not set: ''
Position Capture: ''
-Latitude: 緯度
-Longitude: 経度
-Height: 高さ
-Current Position: 現地
-Current Rotation: 現在のローテーション
-Heading: ヘッディング
-Pitch: ピッチ
-Roll: ロール
-Camera Position Editor: カメラ位置の編集
-Location: 位置
+Latitude: ''
+Longitude: ''
+Height: ''
+Current Position: ''
+Current Rotation: ''
+Heading: ''
+Pitch: ''
+Roll: ''
+Camera Position Editor: ''
+Location: ''
value: ''
-Rotation: ローテーション
-Position Set: 設定済み
-Edit: 編集
-Capture: キャプチャ
+Rotation: ''
+Position Set: ''
+Edit: ''
+Capture: ''
Time Period Settings: ''
-'* Start Time': '* 開始時刻'
-Start time for the timeline: タイムラインの開始時刻
-'* Current Time': '* 現在時刻'
-Current time should be between start and end time: 現在時刻は開始時刻と終了時刻の間で設定してください
-'* End Time': '* 終了時刻'
-End time for the timeline: タイムラインの終了時刻
-Please make sure the Current time must between the Start time and End Time.: 現在時刻が開始時刻と終了時刻の間にあることを確認してください
-set: 設定
-Set Time: 時間設定
-Date: 時間帯
-Time: 時間
-Time Zone: 時間帯
-Failed to add one or more assets.: アセットの作成に失敗しました。
-Successfully added one or more assets.: アセットの追加に成功しました。
-Failed to delete one or more assets.: アセットの削除に失敗しまたした。
-One or more assets were successfully deleted.: アセットが削除されました。
-Failed to add layer.: レイヤーの追加に失敗しました。
-Successfully added a new layer: 新しいレイヤーの追加に成功しました。
-Failed to update the layer.: レイヤーのアップデートに失敗しました。
+'* Start Time': ''
+Start time for the timeline: ''
+'* Current Time': ''
+Current time should be between start and end time: ''
+'* End Time': ''
+End time for the timeline: ''
+Please make sure the Current time must between the Start time and End Time.: ''
+set: ''
+Set Time: ''
+Date: ''
+Time: ''
+Time Zone: ''
+Failed to add one or more assets.: ''
+Successfully added one or more assets.: ''
+Failed to delete one or more assets.: ''
+One or more assets were successfully deleted.: ''
+Failed to add layer.: ''
+Successfully added a new layer: ''
+Failed to update the layer.: ''
Successfully updated the layer!: ''
Failed to delete the feature.: ''
Successfully deleted the feature!: ''
-Failed to create block.: ブロックの作成に失敗しました。
-Successfullly created a block!: ブロックの作成に成功しました!
-Failed to delete block.: ブロックの削除に失敗しました。
-Block was successfully deleted.: ブロックの削除に成功しました。
-Failed to move block.: ブロックの移動に失敗しました。
-Block was successfully moved.: ブロックの移動に成功しました。
-Failed to remove the layer.: レイヤーの削除に失敗しました。
+Failed to create block.: ''
+Successfullly created a block!: ''
+Failed to delete block.: ''
+Block was successfully deleted.: ''
+Failed to move block.: ''
+Block was successfully moved.: ''
+Failed to remove the layer.: ''
Successfully removed the layer!: ''
Failed to update the custom property schema.: ''
Successfully updated the custom property schema!: ''
Failed to add layer style.: ''
Successfully added a new layer style!: ''
-Failed to update the layerStyle.: レイヤースタイルのアップデートに失敗しました。
-Successfully updated a the layerStyle!: レイヤースタイルのアップデートに成功しました!
+Failed to update the layerStyle.: ''
+Successfully updated a the layerStyle!: ''
Failed to delete the layer style.: ''
Successfully deleted the layer style!: ''
Failed to update user password.: ''
Successfully updated user password!: ''
Failed to delete user.: ''
Successfully delete user!: ''
+Failed to search user.: ''
Failed to change language.: ''
Successfully updated user language!: ''
Failed to install plugin.: プラグインのインストールに失敗しました。
@@ -414,29 +429,29 @@ Failed to import project.: プロジェクトのインポートに失敗しま
Successfully imported project!: プロジェクトのインポートに成功しました!
Failed to update property.: プロパティのアップデートに失敗しました。
Successfully updated the property value!: ''
-Failed to create story.: ストーリーの作成に失敗しました。
-Successfully created a story!: ストーリーの作成に成功しました!
-Failed to update story.: ストーリーのアップデートに失敗しました。
-Successfully updated a story!: ストーリーのアップデートに成功しました!
-Failed to publish story.: ストーリーの公開に失敗しました。
-Successfully published your story!: ストーリーの公開に成功しました!
-Successfully published your story with search engine indexing!: 検索エンジンのインデックス含めてストーリーの公開に成功しました!
-Successfully unpublished your story. Now nobody can access your story.: ストーリーは非公開状態です。現在、誰も閲覧することはできません。
-Failed to create page.: ページの作成に失敗しました。
-Successfullly created a page!: ページの作成に成功しました!
-Failed to delete page.: ページの削除に失敗しました。
-Page was successfully deleted.: ページの削除に成功しました。
-Failed to move page.: ページの移動に失敗しました。
-Page was successfully moved.: ページの移動に成功しました。
-Failed to update page.: ページのアップデートに失敗しました。
-Successfullly updated a page!: ページのアップデートに成功しました!
-Failed to add widget.: ウィジェットの作成に失敗しました。
-Failed to update widget.: ウィジェットのアップデートに失敗しました。
-Failed to remove widget.: ウィジェットの削除に失敗しました。
-Failed to update widget alignment.: ウィジェットのアラインメントのアップデートに失敗しました。
-Failed to update the widget align system.: ウィジェットのアラインシステムのアップデートに失敗しました。
-Failed to create workspace.: ワークスペースの作成に失敗しました。
-Successfully created workspace!: 新しいワークスペースの作成に成功しました!
+Failed to create story.: ''
+Successfully created a story!: ''
+Failed to update story.: ''
+Successfully updated a story!: ''
+Failed to publish story.: ''
+Successfully published your story!: ''
+Successfully published your story with search engine indexing!: ''
+Successfully unpublished your story. Now nobody can access your story.: ''
+Failed to create page.: ''
+Successfullly created a page!: ''
+Failed to delete page.: ''
+Page was successfully deleted.: ''
+Failed to move page.: ''
+Page was successfully moved.: ''
+Failed to update page.: ''
+Successfullly updated a page!: ''
+Failed to add widget.: ''
+Failed to update widget.: ''
+Failed to remove widget.: ''
+Failed to update widget alignment.: ''
+Failed to update the widget align system.: ''
+Failed to create workspace.: ''
+Successfully created workspace!: ''
Failed to delete workspace.: ''
Successfully deleted workspace!: ''
Failed to update workspace.: ''
@@ -447,4 +462,3 @@ Failed to remove member from workspace.: ''
Successfully removed member from workspace!: ''
Failed to update member in workspace.: ''
Successfully updated member in workspace!: ''
-Style_: スタイル_
diff --git a/web/src/services/state/index.ts b/web/src/services/state/index.ts
index aea140a30a..02bcfe5806 100644
--- a/web/src/services/state/index.ts
+++ b/web/src/services/state/index.ts
@@ -1,6 +1,8 @@
import { atom, useAtom, useSetAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
+import { TeamMember } from "../gql";
+
export * from "./devPlugins";
export { default as useSetError, useError } from "./gqlErrorHandling";
@@ -49,7 +51,7 @@ export type Policy = {
export type Workspace = {
id: string;
name: string;
- members?: any[];
+ members?: TeamMember[];
assets?: any;
projects?: any;
personal?: boolean;