{
+
+
+ const userStore = useStore(UserService)
+ const userService = useService(UserService)
+
+ const userSession = useUserSession()
+ const currentUser = React.useMemo(() => userService.getById(userSession?.user?.id), [userStore, userSession])
+
+ const leftContent =
+
+
+
+
+ {currentUser?.firstname} {currentUser?.lastname}
+
+
+
+
+ @{currentUser?.username}
+
+
+
+ {currentUser?.email}
+
+
+
+ BASIC
+
+
+
+ Edit Profile
+
+
+ Upgrade to Pro
+
+
+
+
+
+
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/src/packages/ce/src/namespace/util/Namespace.name.util.ts b/src/packages/ce/src/namespace/util/Namespace.name.util.ts
new file mode 100644
index 00000000..16bcc663
--- /dev/null
+++ b/src/packages/ce/src/namespace/util/Namespace.name.util.ts
@@ -0,0 +1,21 @@
+import {Namespace} from "@code0-tech/sagittarius-graphql-types";
+import {OrganizationService} from "@edition/organization/services/Organization.service";
+import {UserService} from "@edition/user/services/User.service";
+
+/**
+ * A namespace has no own name. Its display name is derived from its parent:
+ * the organization name or the username of the user it belongs to.
+ */
+export const getNamespaceName = (
+ namespace: Namespace | undefined | null,
+ organizationService: OrganizationService,
+ userService: UserService
+): string | undefined => {
+ if (!namespace?.parent) return undefined
+ if (namespace.parent.__typename === "Organization")
+ return organizationService.getById(namespace.parent.id)?.name ?? undefined
+ if (namespace.parent.__typename === "User")
+ return userService.getById(namespace.parent.id)?.username ?
+ `@${userService.getById(namespace.parent.id)?.username}'s workspace` : undefined
+ return undefined
+}
diff --git a/src/packages/ce/src/namespace/views/NamespaceOverviewOrganizationLeftView.tsx b/src/packages/ce/src/namespace/views/NamespaceOverviewOrganizationLeftView.tsx
index 3aa6c1e3..2a643c17 100644
--- a/src/packages/ce/src/namespace/views/NamespaceOverviewOrganizationLeftView.tsx
+++ b/src/packages/ce/src/namespace/views/NamespaceOverviewOrganizationLeftView.tsx
@@ -63,9 +63,11 @@ export const NamespaceOverviewOrganizationLeftView: React.FC = () => {
{parentOrganization?.name}
- Edit Organization
+
+ Edit Organization
+
-
+
Upgrade to Team
diff --git a/src/packages/ce/src/namespace/views/NamespaceTabView.tsx b/src/packages/ce/src/namespace/views/NamespaceTabView.tsx
deleted file mode 100644
index 81af8d27..00000000
--- a/src/packages/ce/src/namespace/views/NamespaceTabView.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-"use client"
-
-import React from "react";
-import {Button, useService, useStore} from "@code0-tech/pictor";
-import {Tab, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
-import {IconFolders, IconHome, IconServer, IconSettings, IconUserCog, IconUsers} from "@tabler/icons-react";
-import {useParams, usePathname, useRouter} from "next/navigation";
-import {NamespaceService} from "@edition/namespace/services/Namespace.service";
-import {OrganizationService} from "@edition/organization/services/Organization.service";
-
-export const NamespaceTabView: React.FC = () => {
-
- const pathname = usePathname()
- const router = useRouter()
- const params = useParams()
- const namespaceId = params.namespaceId as any
-
- const namespaceService = useService(NamespaceService)
- const namespaceStore = useStore(NamespaceService)
- const organizationService = useService(OrganizationService)
- const organizationStore = useStore(OrganizationService)
-
- const namespace = React.useMemo(() => namespaceService.getById(`gid://sagittarius/Namespace/${namespaceId}`), [namespaceStore, namespaceId])
- const parentOrganization = React.useMemo(() => namespace?.parent?.__typename === "Organization" ? organizationService.getById(namespace?.parent?.id) : null, [organizationStore, namespace])
-
- const baseLink = `/namespace/${namespaceId}`
- const defaultValue = pathname.includes("projects") ? "projects"
- : pathname.includes("members") ? "members"
- : pathname.includes("roles") ? "roles"
- : pathname.includes("runtimes") ? "runtimes"
- : pathname.includes("settings") ? "settings"
- : "overview"
-
- const settings = React.useMemo(() => {
- return (
- (parentOrganization && (parentOrganization.userAbilities?.deleteOrganization
- || parentOrganization?.userAbilities?.updateOrganization))
- || namespace?.userAbilities?.createLicense
- //TODO add license check for enterprise features
- ) ? (
-
- router.push(`${baseLink}/settings`)}>
-
-
-
- ) : null
- }, [namespace, parentOrganization])
-
- return
-
-
- router.push(baseLink)}>
-
-
-
-
- router.push(`${baseLink}/projects`)}>
-
-
-
-
- router.push(`${baseLink}/members`)}>
-
-
-
-
- router.push(`${baseLink}/roles`)}>
-
-
-
-
- router.push(`${baseLink}/runtimes`)}>
-
-
-
- {settings}
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/namespace/views/NamespacesTopView.tsx b/src/packages/ce/src/namespace/views/NamespacesTopView.tsx
new file mode 100644
index 00000000..e6b84f14
--- /dev/null
+++ b/src/packages/ce/src/namespace/views/NamespacesTopView.tsx
@@ -0,0 +1,20 @@
+"use client"
+
+import React from "react";
+import {useRouter} from "next/navigation";
+import {NamespaceDataTableComponent} from "@edition/namespace/components/NamespaceDataTableComponent";
+
+export const NamespacesTopView = () => {
+
+ const router = useRouter()
+
+ return <>
+ index < 5} onSelect={(namespace) => {
+ const number = namespace?.id?.match(/Namespace\/(\d+)$/)?.[1]
+ router.push(`/namespace/${number}`)
+ }}/>
+ >
+
+}
diff --git a/src/packages/ce/src/namespace/views/NamespacesView.tsx b/src/packages/ce/src/namespace/views/NamespacesView.tsx
new file mode 100644
index 00000000..c39b3329
--- /dev/null
+++ b/src/packages/ce/src/namespace/views/NamespacesView.tsx
@@ -0,0 +1,124 @@
+"use client"
+
+import {
+ Button,
+ Flex,
+ Menu,
+ MenuCheckboxItem,
+ MenuContent,
+ MenuPortal,
+ MenuTrigger,
+ Spacing,
+ Text
+} from "@code0-tech/pictor";
+import Link from "next/link";
+import React from "react";
+import {useRouter} from "next/navigation";
+import {NamespaceDataTableComponent} from "@edition/namespace/components/NamespaceDataTableComponent";
+import {
+ NamespaceDataTableFilterInputComponent
+} from "@edition/namespace/components/NamespaceDataTableFilterInputComponent";
+import {DataTableFilterProps, DataTableSortProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
+import {ButtonGroup} from "@code0-tech/pictor/dist/components/button-group/ButtonGroup";
+import {IconMinus, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
+
+export const NamespacesView = () => {
+
+ const router = useRouter()
+
+ const [filter, setFilter] = React.useState({})
+ const [sort, setSort] = React.useState({})
+
+ return <>
+
+
+
+ Workspaces
+
+
+
+ Workspaces bring your organizations, projects and flows together in one shared place
+ so you and your team can create, organize and manage everything from a single central
+ view
+
+
+
+
+ Create
+
+
+
+ Sort
+
+
+
+ {
+ if (sort["name"] === undefined)
+ setSort(prev => ({...prev, name: "asc"}))
+ else if (sort["name"] === "asc")
+ setSort(prev => ({...prev, name: "desc"}))
+ else
+ setSort(prev => ({...prev, name: undefined}))
+
+ event.preventDefault()
+ event.stopPropagation()
+ }}>
+ {sort["name"] === undefined ? : sort["name"] === "asc" ?
+ : }
+ Name
+
+ {
+ if (sort["createdAt"] === undefined)
+ setSort(prev => ({...prev, createdAt: "asc"}))
+ else if (sort["createdAt"] === "asc")
+ setSort(prev => ({...prev, createdAt: "desc"}))
+ else
+ setSort(prev => ({...prev, createdAt: undefined}))
+
+ event.preventDefault()
+ event.stopPropagation()
+ }}>
+ {sort["createdAt"] === undefined ?
+ : sort["createdAt"] === "asc" ?
+ : }
+ Created At
+
+ {
+ if (sort["updatedAt"] === undefined)
+ setSort(prev => ({...prev, updatedAt: "asc"}))
+ else if (sort["updatedAt"] === "asc")
+ setSort(prev => ({...prev, updatedAt: "desc"}))
+ else
+ setSort(prev => ({...prev, updatedAt: undefined}))
+
+ event.preventDefault()
+ event.stopPropagation()
+ }}>
+ {sort["updatedAt"] === undefined ?
+ : sort["updatedAt"] === "asc" ?
+ : }
+ Updated At
+
+
+
+
+
+
+
+
+ setFilter(filter)}/>
+
+
+ {
+ const number = namespace?.id?.match(/Namespace\/(\d+)$/)?.[1]
+ router.push(`/namespace/${number}`)
+ }}/>
+ >
+
+}
diff --git a/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx b/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx
index c5971720..b3fc2acd 100644
--- a/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx
+++ b/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx
@@ -29,7 +29,7 @@ export const OrganizationCreatePage = () => {
name: values.name as unknown as string
}).then(payload => {
if ((payload?.errors?.length ?? 0) <= 0) {
- router.push("/organizations")
+ router.push("/workspaces")
}
})
})
@@ -55,7 +55,7 @@ export const OrganizationCreatePage = () => {
{...inputs.getInputProps("name")}/>
-
+
Go back to organizations
diff --git a/src/packages/ce/src/organization/views/OrganizationsTopView.tsx b/src/packages/ce/src/organization/views/OrganizationsTopView.tsx
index 60a36763..bab3740c 100644
--- a/src/packages/ce/src/organization/views/OrganizationsTopView.tsx
+++ b/src/packages/ce/src/organization/views/OrganizationsTopView.tsx
@@ -1,6 +1,5 @@
"use client"
-import {Flex, Spacing, Text} from "@code0-tech/pictor";
import React from "react";
import {useRouter} from "next/navigation";
import {OrganizationDataTableComponent} from "@edition/organization/components/OrganizationDataTableComponent";
@@ -10,16 +9,6 @@ export const OrganizationsTopView = () => {
const router = useRouter()
return <>
-
-
- Top organizations
-
-
- Manage organizations that you belong to. You can create new organizations and switch between them.
-
-
-
- {/**TODO: use namespaceId*/}
index < 5} onSelect={(organization) => {
diff --git a/src/packages/ce/src/organization/views/OrganizationsView.tsx b/src/packages/ce/src/organization/views/OrganizationsView.tsx
index 03e6c5d1..3f7cb0d1 100644
--- a/src/packages/ce/src/organization/views/OrganizationsView.tsx
+++ b/src/packages/ce/src/organization/views/OrganizationsView.tsx
@@ -40,7 +40,7 @@ export const OrganizationsView = () => {
-
+
Create
diff --git a/src/packages/ce/src/project/services/fragments/Project.fragment.graphql b/src/packages/ce/src/project/services/fragments/Project.fragment.graphql
index 287c91f2..f28e8a0b 100644
--- a/src/packages/ce/src/project/services/fragments/Project.fragment.graphql
+++ b/src/packages/ce/src/project/services/fragments/Project.fragment.graphql
@@ -17,6 +17,7 @@ fragment Project on NamespaceProject {
nodes {
__typename
id
+ createdAt
}
pageInfo {
__typename
diff --git a/src/packages/ce/src/project/views/PersonalProjectsView.tsx b/src/packages/ce/src/project/views/PersonalProjectsView.tsx
index b4b719f8..013a4c7f 100644
--- a/src/packages/ce/src/project/views/PersonalProjectsView.tsx
+++ b/src/packages/ce/src/project/views/PersonalProjectsView.tsx
@@ -46,91 +46,6 @@ export const PersonalProjectsView: React.FC = () => {
}, [currentUser, filter, sort])
return <>
-
-
-
-
- Personal projects
-
-
- Projects created in your personal namespace. You can also create organization projects if you are a
- member of any organization.
-
-
-
-
- Create
-
-
-
- Sort
-
-
-
- {
- if (sort["name"] === undefined)
- setSort(prev => ({...prev, name: "asc"}))
- else if (sort["name"] === "asc")
- setSort(prev => ({...prev, name: "desc"}))
- else
- setSort(prev => ({...prev, name: undefined}))
-
- event.preventDefault()
- event.stopPropagation()
- }}>
- {sort["name"] === undefined ? : sort["name"] === "asc" ?
- : }
- Name
-
- {
- if (sort["createdAt"] === undefined)
- setSort(prev => ({...prev, createdAt: "asc"}))
- else if (sort["createdAt"] === "asc")
- setSort(prev => ({...prev, createdAt: "desc"}))
- else
- setSort(prev => ({...prev, createdAt: undefined}))
-
- event.preventDefault()
- event.stopPropagation()
- }}>
- {sort["createdAt"] === undefined ?
- : sort["createdAt"] === "asc" ?
- : }
- Created At
-
- {
- if (sort["updatedAt"] === undefined)
- setSort(prev => ({...prev, updatedAt: "asc"}))
- else if (sort["updatedAt"] === "asc")
- setSort(prev => ({...prev, updatedAt: "desc"}))
- else
- setSort(prev => ({...prev, updatedAt: undefined}))
-
- event.preventDefault()
- event.stopPropagation()
- }}>
- {sort["updatedAt"] === undefined ?
- : sort["updatedAt"] === "asc" ?
- : }
- Updated At
-
-
-
-
-
-
-
-
-
{projectsList}
>
diff --git a/src/packages/ce/src/project/views/ProjectTabView.tsx b/src/packages/ce/src/project/views/ProjectTabView.tsx
deleted file mode 100644
index 30416b3a..00000000
--- a/src/packages/ce/src/project/views/ProjectTabView.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React from "react";
-import {useParams, usePathname, useRouter} from "next/navigation";
-import {Tab, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
-import {Button, Text, Tooltip, TooltipContent, TooltipPortal, TooltipTrigger} from "@code0-tech/pictor";
-import {IconBox, IconHome, IconServer, IconSettings} from "@tabler/icons-react";
-
-export const ProjectTabView: React.FC = () => {
- const pathname = usePathname()
- const router = useRouter()
- const params = useParams()
- const defaultValue = pathname.includes("flow") ? "flow"
- : pathname.includes("module") ? "module"
- : pathname.includes("runtime") ? "runtime" :
- "settings"
-
- const namespaceId = params.namespaceId as any
- const projectId = params.projectId as any
- const flowId = params.flowId as any
-
- return
-
-
-
-
- {
- router.push(`/namespace/${namespaceId}/project/${projectId}/flow`)
- }}>
-
-
-
-
-
-
- Flows
-
-
-
-
-
-
- router.push(`/namespace/${namespaceId}/project/${projectId}/runtime`)}>
-
-
-
-
-
-
- Assigned runtimes
-
-
-
-
-
-
- router.push(`/namespace/${namespaceId}/project/${projectId}/module`)}>
-
-
-
-
-
-
- Plugins
-
-
-
-
-
-
- router.push(`/namespace/${namespaceId}/project/${projectId}/settings`)}>
-
-
-
-
-
-
- Settings
-
-
-
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/project/views/ProjectsView.tsx b/src/packages/ce/src/project/views/ProjectsView.tsx
index 65c76a19..71f73b67 100644
--- a/src/packages/ce/src/project/views/ProjectsView.tsx
+++ b/src/packages/ce/src/project/views/ProjectsView.tsx
@@ -43,22 +43,23 @@ export const ProjectsView: React.FC = () => {
return <>
-
-
-
+
+
+
Projects
-
- Manage projects that you belong to. You can create new projects and switch between them.
+
+
+ Manage projects that you belong to. You can create new projects and switch between them.
-
+
- Create
+ Create
- Sort
+ Sort
diff --git a/src/packages/ce/src/user/components/UserEditDialogComponent.tsx b/src/packages/ce/src/user/components/UserEditDialogComponent.tsx
index 75bebbe3..f14600d1 100644
--- a/src/packages/ce/src/user/components/UserEditDialogComponent.tsx
+++ b/src/packages/ce/src/user/components/UserEditDialogComponent.tsx
@@ -2,8 +2,8 @@
import React from "react";
import {
+ Badge,
Button,
- ButtonGroup,
Card,
Col,
Dialog,
@@ -31,7 +31,7 @@ import {Tab, TabContent, TabList, TabTrigger} from "@code0-tech/pictor/dist/comp
import {User, UsersUpdateInput} from "@code0-tech/sagittarius-graphql-types";
import {UserService} from "@edition/user/services/User.service";
import {addIslandSuccessNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
-import {IconAt, IconLock, IconMail, IconUser, IconX} from "@tabler/icons-react";
+import {IconAt, IconLock, IconMail, IconUser} from "@tabler/icons-react";
import {Layout} from "@code0-tech/pictor/dist/components/layout/Layout";
import {motion} from "framer-motion";
@@ -151,23 +151,22 @@ export const UserEditDialogComponent: React.FC = (
}}
initial={{
width: "200px",
- opacity: 0.25,
}}
whileInView={{
width: "200px",
- opacity: 0.25,
}}
whileHover={{
width: "250px",
- opacity: 1,
}}
style={{
padding: "0.7rem",
- marginTop: "0.55rem",
- height: "100%"
+ paddingTop: "1rem",
+ height: "100%",
+ display: "flex",
+ flexDirection: "column"
}}
>
-
+
Settings of @{user?.username ?? ""}
@@ -193,23 +192,24 @@ export const UserEditDialogComponent: React.FC = (
+
+
+ Close
+
+ ESC
+
+
+
}>
General
-
-
- Save changes
-
-
-
-
-
-
-
+
+ Save changes
+
@@ -254,18 +254,10 @@ export const UserEditDialogComponent: React.FC = (
Permissions
-
-
- Save changes
-
-
-
-
-
-
-
-
+
+ Save changes
+
@@ -285,17 +277,10 @@ export const UserEditDialogComponent: React.FC = (
Security
-
-
- Save changes
-
-
-
-
-
-
-
+
+ Save changes
+
diff --git a/src/packages/ce/src/user/components/UserMenuComponent.tsx b/src/packages/ce/src/user/components/UserMenuComponent.tsx
index 0b4c1885..d69c9782 100644
--- a/src/packages/ce/src/user/components/UserMenuComponent.tsx
+++ b/src/packages/ce/src/user/components/UserMenuComponent.tsx
@@ -3,7 +3,8 @@
import React from "react"
import {Scalars} from "@code0-tech/sagittarius-graphql-types";
import {
- Avatar, Button,
+ Avatar,
+ Button,
Flex,
Menu,
MenuContent,
@@ -29,7 +30,7 @@ const UserMenuComponent: React.FC = props => {
return (
-
+
diff --git a/src/packages/ce/src/user/services/User.service.ts b/src/packages/ce/src/user/services/User.service.ts
index f5bb9b39..288dbf67 100644
--- a/src/packages/ce/src/user/services/User.service.ts
+++ b/src/packages/ce/src/user/services/User.service.ts
@@ -61,12 +61,26 @@ export class UserService extends ReactiveArrayService {
values(): User[] {
if (super.values().length > 0) return super.values();
this.client.query({
- query: usersQuery
+ query: usersQuery,
+ variables: {
+ firstUser: 50,
+ afterUser: null,
+ firstIdentity: 50,
+ afterIdentity: null,
+ firstSession: 50,
+ afterSession: null,
+ firstNamespaceMembership: 50,
+ afterNamespaceMembership: null,
+ }
}).then(result => {
const data = result.data
if (!data) return
- if (data && data.currentUser && !this.hasById(data.currentUser.id)) this.set(this.i++, new View(data.currentUser))
+ if (data && data.currentUser) {
+ const currentUser = data.currentUser
+ const index = super.values().findIndex(user => user && user.id === currentUser.id)
+ this.set(index >= 0 ? index : this.i++, new View(currentUser))
+ }
if (data.users && data.users.nodes) {
data.users.nodes.forEach((user) => {
if (user && !(user.id === data.currentUser?.id) && !this.hasById(user.id)) this.set(this.i++, new View(user))
@@ -77,7 +91,7 @@ export class UserService extends ReactiveArrayService {
}
getById(id: User["id"]): User | undefined {
- const user = this.values().find(user => user && user.id === id)
+ const user = super.values().find(user => user && user.id === id)
if (user) return user
if (id) {
diff --git a/src/packages/ce/src/user/services/fragments/User.fragment.graphql b/src/packages/ce/src/user/services/fragments/User.fragment.graphql
index d2cc7939..3aa47f58 100644
--- a/src/packages/ce/src/user/services/fragments/User.fragment.graphql
+++ b/src/packages/ce/src/user/services/fragments/User.fragment.graphql
@@ -14,6 +14,19 @@ fragment User on User {
namespace {
id
}
+ namespaceMemberships (first: $firstNamespaceMembership, after: $afterNamespaceMembership) {
+ count
+ nodes {
+ id
+ namespace {
+ id
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
identities (first: $firstIdentity, after: $afterIdentity) {
count
nodes {
diff --git a/src/packages/ce/src/user/services/queries/Users.query.graphql b/src/packages/ce/src/user/services/queries/Users.query.graphql
index 6cac0624..242ba7e0 100644
--- a/src/packages/ce/src/user/services/queries/Users.query.graphql
+++ b/src/packages/ce/src/user/services/queries/Users.query.graphql
@@ -1,7 +1,16 @@
#import '../fragments/User.fragment.graphql'
#import '../fragments/User.basic.fragment.graphql'
-query Users($firstUser: Int, $afterUser: String, $firstIdentity: Int, $afterIdentity: String, $firstSession: Int, $afterSession: String) {
+query Users(
+ $firstUser: Int,
+ $afterUser: String,
+ $firstIdentity: Int,
+ $afterIdentity: String,
+ $firstSession: Int,
+ $afterSession: String,
+ $firstNamespaceMembership: Int,
+ $afterNamespaceMembership: String
+) {
currentUser {
...User
}