From 426747590f31134551bb549489a1d59b1fb0b300 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:00:51 +0200 Subject: [PATCH 01/15] feat: add runtimeMaxHeartbeatIntervalMinutes to ApplicationUpdate mutation for enhanced configuration options --- .../services/mutations/Application.update.mutation.graphql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql b/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql index 018940ab..030ac89f 100644 --- a/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql +++ b/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql @@ -1,4 +1,4 @@ -mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreationRestricted: Boolean, $adminStatusVisible: Boolean, $termsAndConditionsUrl: String, $privacyUrl: String, $legalNoticeUrl: String) { +mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreationRestricted: Boolean, $adminStatusVisible: Boolean, $termsAndConditionsUrl: String, $privacyUrl: String, $legalNoticeUrl: String, $runtimeMaxHeartbeatIntervalMinutes: Int) { applicationSettingsUpdate(input: { userRegistrationEnabled: $userRegistrationEnabled organizationCreationRestricted: $organizationCreationRestricted @@ -6,6 +6,7 @@ mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreat termsAndConditionsUrl: $termsAndConditionsUrl privacyUrl: $privacyUrl legalNoticeUrl: $legalNoticeUrl + runtimeMaxHeartbeatIntervalMinutes: $runtimeMaxHeartbeatIntervalMinutes }) { errors { ...on Error { @@ -24,6 +25,7 @@ mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreat adminStatusVisible organizationCreationRestricted userRegistrationEnabled + runtimeMaxHeartbeatIntervalMinutes } } } \ No newline at end of file From 7b4a87125075a78be3e3fbcd543e3177637b9482 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:01:09 +0200 Subject: [PATCH 02/15] feat: add runtimeMaxHeartbeatIntervalMinutes field to Application fragment for enhanced configuration options --- .../application/services/fragments/Application.fragment.graphql | 1 + .../application/services/fragments/Application.fragment.graphql | 1 + 2 files changed, 2 insertions(+) diff --git a/src/packages/ce/src/application/services/fragments/Application.fragment.graphql b/src/packages/ce/src/application/services/fragments/Application.fragment.graphql index 38dc6bfb..f4d55281 100644 --- a/src/packages/ce/src/application/services/fragments/Application.fragment.graphql +++ b/src/packages/ce/src/application/services/fragments/Application.fragment.graphql @@ -16,6 +16,7 @@ fragment Application on Application { adminStatusVisible organizationCreationRestricted userRegistrationEnabled + runtimeMaxHeartbeatIntervalMinutes } userAbilities { __typename diff --git a/src/packages/ee/src/application/services/fragments/Application.fragment.graphql b/src/packages/ee/src/application/services/fragments/Application.fragment.graphql index f96c379b..d2929e0e 100644 --- a/src/packages/ee/src/application/services/fragments/Application.fragment.graphql +++ b/src/packages/ee/src/application/services/fragments/Application.fragment.graphql @@ -32,6 +32,7 @@ fragment Application on Application { adminStatusVisible organizationCreationRestricted userRegistrationEnabled + runtimeMaxHeartbeatIntervalMinutes } userAbilities { __typename From 7b9828277fc1eda23fc4d2f5844c1e28123d48c6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:01:15 +0200 Subject: [PATCH 03/15] feat: update @code0-tech/triangulum to version 0.26.0 for improved functionality --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 507ee59a..18f8c601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.11.0", - "@code0-tech/triangulum": "^0.25.8", + "@code0-tech/triangulum": "^0.26.0", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lint": "^6.9.5", "@icons-pack/react-simple-icons": "^13.13.0", @@ -1940,17 +1940,17 @@ } }, "node_modules/@code0-tech/sagittarius-graphql-types": { - "version": "0.0.0-experimental-2616186698-14d97e4ac998067751f9cbd9f8fc05c6fed628fe", - "resolved": "https://registry.npmjs.org/@code0-tech/sagittarius-graphql-types/-/sagittarius-graphql-types-0.0.0-experimental-2616186698-14d97e4ac998067751f9cbd9f8fc05c6fed628fe.tgz", - "integrity": "sha512-8TqdH9pFs1HQjxqGtxgFslnKdvlVBaKooKrWvpPK4LGdYwu8n1MZFSqXu6sl4+OvrPaTEQxqhGdScjut+da9DA==", + "version": "0.0.0-experimental-2668386432-315fff7e9283580109793944cb24db116d9cc264", + "resolved": "https://registry.npmjs.org/@code0-tech/sagittarius-graphql-types/-/sagittarius-graphql-types-0.0.0-experimental-2668386432-315fff7e9283580109793944cb24db116d9cc264.tgz", + "integrity": "sha512-LsWTxLYxdmCcsS7icdP7/DedDZJFPucx0k0ryKX1bwAqhcT2HPXoC/9sKSE1hl7CKcv1BlbP5PKigLp4kmHXtg==", "peer": true }, "node_modules/@code0-tech/triangulum": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.25.8.tgz", - "integrity": "sha512-qEte2foHL78W0YW+4hVSjLtNgts1DnLXE6AMymypWspzzpKpzMLXZ0B2aRwBRaZL6yuh5GmG7nsG03Q9OAgxXg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.26.0.tgz", + "integrity": "sha512-GJsy3/obzPP6khmw2fy9/7Bla4S5zwaobps2XF+WUQBYEjbuKz/+53gvIDECXBkCgglP0dDLrQbXnRXinUawCw==", "peerDependencies": { - "@code0-tech/sagittarius-graphql-types": "0.0.0-experimental-2616186698-14d97e4ac998067751f9cbd9f8fc05c6fed628fe", + "@code0-tech/sagittarius-graphql-types": "0.0.0-experimental-2668386432-315fff7e9283580109793944cb24db116d9cc264", "@typescript/vfs": "^1.6.4", "lossless-json": "^4.3.0", "typescript": "^5.9.3 || ^6.0.2" diff --git a/package.json b/package.json index 162bbe07..a1926b03 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.11.0", - "@code0-tech/triangulum": "^0.25.8", + "@code0-tech/triangulum": "^0.26.0", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lint": "^6.9.5", "@icons-pack/react-simple-icons": "^13.13.0", From 4ca08ade7f699a9dc9e19065a84da146db38a751 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:04:03 +0200 Subject: [PATCH 04/15] feat: add runtimeMaxHeartbeatIntervalMinutes input to ApplicationGeneralSettingsView for better configuration --- .../views/ApplicationGeneralSettingsView.tsx | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/packages/ce/src/application/views/ApplicationGeneralSettingsView.tsx b/src/packages/ce/src/application/views/ApplicationGeneralSettingsView.tsx index 4ff246b6..11d809ee 100644 --- a/src/packages/ce/src/application/views/ApplicationGeneralSettingsView.tsx +++ b/src/packages/ce/src/application/views/ApplicationGeneralSettingsView.tsx @@ -1,5 +1,5 @@ import React, {startTransition} from "react"; -import {Badge, Button, Card, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor"; +import {Badge, Button, Card, Flex, NumberInput, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor"; import CardSection from "@code0-tech/pictor/dist/components/card/CardSection"; import {TabContent} from "@code0-tech/pictor/dist/components/tab/Tab"; import {ApplicationService} from "@edition/application/services/Application.service"; @@ -20,20 +20,30 @@ export const ApplicationGeneralSettingsView: React.FC = () => { legalNoticeUrl: !!application?.settings?.legalNoticeUrl ? application?.settings?.legalNoticeUrl : null, privacyUrl: !!application?.settings?.privacyUrl ? application?.settings?.privacyUrl : null, termsAndConditionsUrl: !!application?.settings?.termsAndConditionsUrl ? application?.settings?.termsAndConditionsUrl : null, + runtimeMaxHeartbeatIntervalMinutes: application?.settings?.runtimeMaxHeartbeatIntervalMinutes != null + ? String(application?.settings?.runtimeMaxHeartbeatIntervalMinutes) + : null, }), [application] ) + // The +/- buttons of NumberInput write to the input element directly without + // triggering a React change event, so the useForm value goes stale. Read the + // value from the element itself on submit instead. + const runtimeHeartbeatInputRef = React.useRef(null) + const [inputs, validate] = useForm({ useInitialValidation: false, initialValues: initialValues, validate: {}, onSubmit: (values) => { + const runtimeMaxHeartbeatIntervalMinutes = runtimeHeartbeatInputRef.current?.value startTransition(() => { applicationService.applicationUpdate({ legalNoticeUrl: !!values.legalNoticeUrl ? values.legalNoticeUrl : null, privacyUrl: !!values.privacyUrl ? values.privacyUrl : null, termsAndConditionsUrl: !!values.termsAndConditionsUrl ? values.termsAndConditionsUrl : null, + runtimeMaxHeartbeatIntervalMinutes: !!runtimeMaxHeartbeatIntervalMinutes ? Number(runtimeMaxHeartbeatIntervalMinutes) : null, }).then(payload => { if ((payload?.errors?.length ?? 0) <= 0) { addIslandSuccessNotification({ @@ -112,5 +122,24 @@ export const ApplicationGeneralSettingsView: React.FC = () => { + + + Runtimes + + + + + + + + Max heartbeat interval + The maximum amount of minutes a runtime is + shown as connected after the last heartbeat. + + + + + } \ No newline at end of file From c63389e03b8396b4a535433afacba347c419dd9e Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:04:08 +0200 Subject: [PATCH 05/15] feat: add additional error codes for improved user feedback --- src/app/layout.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b00b68d5..04197b57 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -39,8 +39,10 @@ const inter = Inter({ */ const ErrorCodeDescription: Record = { CANNOT_DELETE_LAST_ADMIN_ROLE: "Cannot delete role", + CANNOT_MODERATE_USER: "Cannot moderate user", CANNOT_MODIFY_ADMIN: "Cannot modify admin", CANNOT_MODIFY_OWN_ADMIN: "Cannot modify self", + CANNOT_MODIFY_OWN_BLOCKED_STATUS: "Cannot block self", CANNOT_REMOVE_LAST_ADMINISTRATOR: "Cannot remove admin", CANNOT_REMOVE_LAST_ADMIN_ABILITY: "Cannot remove ability", CYCLIC_DATA_TYPE_REFERENCE: "Dependency cycle detected", @@ -112,11 +114,11 @@ const ErrorCodeDescription: Record = { NAMESPACE_ROLE_NOT_FOUND: "Namespace role missing", NODE_NOT_FOUND: "Node not found", NO_DATA_TYPE_FOR_IDENTIFIER: "No data type", + NO_DEFINITIONS: "No definitions available", NO_FREE_LICENSE_SEATS: "No license seats", NO_PRIMARY_RUNTIME: "No primary runtime", ORGANIZATION_NOT_FOUND: "Organization not found", OUTDATED_DEFINITION: "Outdated definition", - PRIMARY_LEVEL_NOT_FOUND: "Primary level missing", PROJECT_NOT_FOUND: "Project not found", REFERENCED_VALUE_NOT_FOUND: "Referenced value missing", REGISTRATION_DISABLED: "Registration disabled", @@ -125,11 +127,10 @@ const ErrorCodeDescription: Record = { RUNTIME_NOT_ASSIGNED: "Runtime not assigned", RUNTIME_NOT_COMPATIBLE: "Runtime not compatible", RUNTIME_NOT_FOUND: "Runtime not found", - SECONDARY_LEVEL_NOT_FOUND: "Secondary level missing", - TERTIARY_LEVEL_EXCEEDS_PARAMETERS: "Tertiary level exceeded", TOTP_SECRET_ALREADY_SET: "TOTP already set", UNMODIFIABLE_FIELD: "Unmodifiable field", UNSUPPORTED_AUTHENTICATION: "Unsupported authentication", + USER_BLOCKED: "User blocked", USER_NOT_FOUND: "User not found", USER_SESSION_NOT_FOUND: "Session not found", WRONG_TOTP: "Wrong TOTP code", From 9a7fb236bc721880a100292ba1c08b6a8c03af06 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:29:22 +0200 Subject: [PATCH 06/15] feat: fix flow settings node mapping to use correct property --- src/packages/ce/src/flow/services/Flow.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/flow/services/Flow.service.ts b/src/packages/ce/src/flow/services/Flow.service.ts index 1e38d5d2..4e21c518 100644 --- a/src/packages/ce/src/flow/services/Flow.service.ts +++ b/src/packages/ce/src/flow/services/Flow.service.ts @@ -314,7 +314,7 @@ export class FlowService extends ReactiveArrayService { + flow.settings.nodes = flowType?.flowTypeSettings?.nodes?.map((flowTypeSetting, index) => { const flowSetting = flow.settings?.nodes?.[index] if (!flowSetting) { return { From dccd6f5c14e0f0d216bb25d985e00106edc92a88 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:29:34 +0200 Subject: [PATCH 07/15] feat: fix flow settings mapping to use correct nodes property --- .../ce/src/flow/components/FlowCreateDialogComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/flow/components/FlowCreateDialogComponent.tsx b/src/packages/ce/src/flow/components/FlowCreateDialogComponent.tsx index c2fa9782..2865408d 100644 --- a/src/packages/ce/src/flow/components/FlowCreateDialogComponent.tsx +++ b/src/packages/ce/src/flow/components/FlowCreateDialogComponent.tsx @@ -99,7 +99,7 @@ export const FlowCreateDialogComponent: React.FC flowService.flowCreate({ // @ts-ignore flow: { - settings: selectedFlowType?.flowTypeSettings?.map(setting => ({ + settings: selectedFlowType?.flowTypeSettings?.nodes?.map(setting => ({ value: setting?.defaultValue !== null && setting?.defaultValue !== undefined ? setting?.defaultValue : null, })) ?? [], name: name, From 19db0503a0f5832d330a82129f4d4b4b12e3c5bb Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:29:49 +0200 Subject: [PATCH 08/15] feat: enhance flowTypeSettings to support pagination with first and after parameters --- .../src/flowtype/services/FlowType.service.ts | 3 ++ .../fragments/FlowType.fragment.graphql | 40 +++++++++++-------- .../services/queries/FlowTypes.query.graphql | 2 + 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/packages/ce/src/flowtype/services/FlowType.service.ts b/src/packages/ce/src/flowtype/services/FlowType.service.ts index 96683f93..703742e3 100644 --- a/src/packages/ce/src/flowtype/services/FlowType.service.ts +++ b/src/packages/ce/src/flowtype/services/FlowType.service.ts @@ -44,6 +44,9 @@ export class FlowTypeService extends ReactiveArrayService { const nodes = res.data?.namespace?.project?.primaryRuntime?.modules?.nodes?.flatMap(module => module?.flowTypes?.nodes ?? []) ?? [] diff --git a/src/packages/ce/src/flowtype/services/fragments/FlowType.fragment.graphql b/src/packages/ce/src/flowtype/services/fragments/FlowType.fragment.graphql index 653703d9..288eb7f4 100644 --- a/src/packages/ce/src/flowtype/services/fragments/FlowType.fragment.graphql +++ b/src/packages/ce/src/flowtype/services/fragments/FlowType.fragment.graphql @@ -32,25 +32,33 @@ fragment FlowType on FlowType { id } signature - flowTypeSettings { + flowTypeSettings (first: $firstFlowTypeSetting, after: $afterFlowTypeSetting) { __typename - id - createdAt - updatedAt - identifier - unique - optional - hidden - defaultValue - names { + count + nodes { __typename - code - content + id + createdAt + updatedAt + identifier + unique + optional + hidden + defaultValue + names { + __typename + code + content + } + descriptions { + __typename + code + content + } } - descriptions { - __typename - code - content + pageInfo { + endCursor + hasNextPage } } editable diff --git a/src/packages/ce/src/flowtype/services/queries/FlowTypes.query.graphql b/src/packages/ce/src/flowtype/services/queries/FlowTypes.query.graphql index 1a2eff1b..a3e25b0e 100644 --- a/src/packages/ce/src/flowtype/services/queries/FlowTypes.query.graphql +++ b/src/packages/ce/src/flowtype/services/queries/FlowTypes.query.graphql @@ -7,6 +7,8 @@ query FlowTypes ( $afterModule: String $firstFlowType: Int! $afterFlowType: String + $firstFlowTypeSetting: Int! + $afterFlowTypeSetting: String ) { namespace(id: $namespaceId) { project(id: $projectId) { From 58aa8f19d2fda5ee2a7d610e6153439329ed5505 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:30:08 +0200 Subject: [PATCH 09/15] feat: update flowTypeSettings to use nodes property for improved data handling --- .../files/FunctionFileTriggerComponent.tsx | 18 +++++++++--------- .../nodes/FunctionNodeTriggerComponent.tsx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/packages/ce/src/function/components/files/FunctionFileTriggerComponent.tsx b/src/packages/ce/src/function/components/files/FunctionFileTriggerComponent.tsx index 419d6762..b1ab42c7 100644 --- a/src/packages/ce/src/function/components/files/FunctionFileTriggerComponent.tsx +++ b/src/packages/ce/src/function/components/files/FunctionFileTriggerComponent.tsx @@ -44,9 +44,9 @@ export const FunctionFileTriggerComponent: React.FC = React.useMemo(() => { const values: Record = {} - definition?.flowTypeSettings?.forEach((setting, index) => { + definition?.flowTypeSettings?.nodes?.forEach((setting, index) => { const flowSetting = instance?.settings?.nodes?.[index] - values[setting.id!] = { + values[setting!.id!] = { __typename: "LiteralValue", value: flowSetting?.value, } @@ -63,7 +63,7 @@ export const FunctionFileTriggerComponent: React.FC { const values: Record = {} - definition?.flowTypeSettings?.forEach((setting, index) => { + definition?.flowTypeSettings?.nodes?.forEach((setting, index) => { values[setting!.id!] = (_: any) => { const validationForSetting = validation?.find(v => v.parameterIndex === index && !v.nodeId) if (validationForSetting) { @@ -77,17 +77,17 @@ export const FunctionFileTriggerComponent: React.FC) => { React.startTransition(async () => { - for (const flowTypeSetting of definition?.flowTypeSettings ?? []) { + for (const flowTypeSetting of definition?.flowTypeSettings?.nodes ?? []) { - if (!changedSettings.current.has(flowTypeSetting.id!)) continue + if (!changedSettings.current.has(flowTypeSetting!.id!)) continue - const index = definition?.flowTypeSettings?.findIndex(p => p?.id === flowTypeSetting?.id) + const index = definition?.flowTypeSettings?.nodes?.findIndex(p => p?.id === flowTypeSetting?.id) if (typeof index !== "number") return - const value = values[flowTypeSetting.id!] + const value = values[flowTypeSetting!.id!] await flowService.setSettingValue(flowId, index, value?.value, definition!) - changedSettings.current.delete(flowTypeSetting.id!) + changedSettings.current.delete(flowTypeSetting!.id!) } }) }, [flowService, definition]) @@ -121,7 +121,7 @@ export const FunctionFileTriggerComponent: React.FCSettings {(() => { - const indexedSettings = definition?.flowTypeSettings + const indexedSettings = definition?.flowTypeSettings?.nodes ?.map((settingDefinition, index) => ({settingDefinition, index})) ?.filter(({settingDefinition}) => settingDefinition && !settingDefinition.hidden) ?? [] diff --git a/src/packages/ce/src/function/components/nodes/FunctionNodeTriggerComponent.tsx b/src/packages/ce/src/function/components/nodes/FunctionNodeTriggerComponent.tsx index baa48095..74c1008a 100644 --- a/src/packages/ce/src/function/components/nodes/FunctionNodeTriggerComponent.tsx +++ b/src/packages/ce/src/function/components/nodes/FunctionNodeTriggerComponent.tsx @@ -117,12 +117,12 @@ export const FunctionNodeTriggerComponent: React.FC splitTemplate(flowType?.displayMessages?.[0]?.content ?? FALLBACK_FLOW_TYPE_DISPLAY_MESSAGE).map(item => { const nodeParameter = flow?.settings?.nodes?.find((_, index) => { - const parameterDefinition = flowType?.flowTypeSettings?.[index] + const parameterDefinition = flowType?.flowTypeSettings?.nodes?.[index] return parameterDefinition?.identifier == item }) - const parameterDefinition = flowType?.flowTypeSettings?.find(pd => pd?.identifier == item) - const parameterIndex = parameterDefinition ? flowType?.flowTypeSettings?.findIndex(p => p?.id === parameterDefinition.id) : undefined + const parameterDefinition = flowType?.flowTypeSettings?.nodes?.find(pd => pd?.identifier == item) + const parameterIndex = parameterDefinition ? flowType?.flowTypeSettings?.nodes?.findIndex(p => p?.id === parameterDefinition.id) : undefined const parameterValidation = validation?.filter(v => v.parameterIndex === parameterIndex && !v.nodeId) const decorationStyle: CSSProperties = parameterValidation?.length From f7b8f25059a9b0dbe63f9f673740f0db559fdfff Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:35:49 +0200 Subject: [PATCH 10/15] feat: add bulk invite mutation for members with error handling --- ...utation.graphql => Member.bulkInvite.mutation.graphql} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename src/packages/ce/src/member/services/mutations/{Member.invite.mutation.graphql => Member.bulkInvite.mutation.graphql} (63%) diff --git a/src/packages/ce/src/member/services/mutations/Member.invite.mutation.graphql b/src/packages/ce/src/member/services/mutations/Member.bulkInvite.mutation.graphql similarity index 63% rename from src/packages/ce/src/member/services/mutations/Member.invite.mutation.graphql rename to src/packages/ce/src/member/services/mutations/Member.bulkInvite.mutation.graphql index fa928e3d..d26bc75e 100644 --- a/src/packages/ce/src/member/services/mutations/Member.invite.mutation.graphql +++ b/src/packages/ce/src/member/services/mutations/Member.bulkInvite.mutation.graphql @@ -1,7 +1,7 @@ #import "../fragments/Member.basic.fragment.graphql" -mutation inviteMember($userId: UserID!, $namespaceId: NamespaceID!) { - namespacesMembersInvite(input: { - userId: $userId, +mutation bulkInviteMembers($userIds: [UserID!]!, $namespaceId: NamespaceID!) { + namespacesMembersBulkInvite(input: { + userIds: $userIds, namespaceId: $namespaceId }) { errors { @@ -12,7 +12,7 @@ mutation inviteMember($userId: UserID!, $namespaceId: NamespaceID!) { } } } - namespaceMember { + namespaceMembers { ...BasicMember } } From 63140e51ae680108d1f50a3645c3bf2d6b254eb1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:36:03 +0200 Subject: [PATCH 11/15] feat: implement member bulk invite mutation with improved member handling --- .../ce/src/member/services/Member.service.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/packages/ce/src/member/services/Member.service.ts b/src/packages/ce/src/member/services/Member.service.ts index 8c1aad45..4a9f7aa4 100644 --- a/src/packages/ce/src/member/services/Member.service.ts +++ b/src/packages/ce/src/member/services/Member.service.ts @@ -7,17 +7,17 @@ import { NamespaceMember, NamespacesMembersAssignRolesInput, NamespacesMembersAssignRolesPayload, + NamespacesMembersBulkInviteInput, + NamespacesMembersBulkInvitePayload, NamespacesMembersDeleteInput, NamespacesMembersDeletePayload, - NamespacesMembersInviteInput, - NamespacesMembersInvitePayload, Query, User } from "@code0-tech/sagittarius-graphql-types" import {GraphqlClient} from "@core/util/graphql-client" import membersQuery from "./queries/Members.query.graphql" import memberAssignRoleMutation from "./mutations/Member.assignRoles.mutation.graphql" import memberDeleteMutation from "./mutations/Member.delete.mutation.graphql" -import memberInviteMutation from "./mutations/Member.invite.mutation.graphql" +import memberBulkInviteMutation from "./mutations/Member.bulkInvite.mutation.graphql" import {View} from "@code0-tech/pictor/dist/utils/view"; export type MemberDependencies = { @@ -120,20 +120,21 @@ export class MemberService extends ReactiveArrayService { - const result = await this.client.mutate({ - mutation: memberInviteMutation, + async memberBulkInvite(payload: NamespacesMembersBulkInviteInput): Promise { + const result = await this.client.mutate({ + mutation: memberBulkInviteMutation, variables: { ...payload } }) - if (result.data && result.data.namespacesMembersInvite && result.data.namespacesMembersInvite.namespaceMember) { - const member = result.data.namespacesMembersInvite.namespaceMember - this.set(this.i++, new View(member)) - } + result.data?.namespacesMembersBulkInvite?.namespaceMembers?.forEach(member => { + if (!this.hasById(member.id)) { + this.set(this.i++, new View(member)) + } + }) - return result.data?.namespacesMembersInvite ?? undefined + return result.data?.namespacesMembersBulkInvite ?? undefined } } \ No newline at end of file From a6f2b7f434d5c5e61b4eec11df139e723a7b653b Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:36:18 +0200 Subject: [PATCH 12/15] feat: refactor member invitation to use bulk invite method for improved efficiency --- src/packages/ce/src/member/pages/MemberAddPage.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/packages/ce/src/member/pages/MemberAddPage.tsx b/src/packages/ce/src/member/pages/MemberAddPage.tsx index efb57e6a..bcb8d24b 100644 --- a/src/packages/ce/src/member/pages/MemberAddPage.tsx +++ b/src/packages/ce/src/member/pages/MemberAddPage.tsx @@ -45,19 +45,17 @@ export const MemberAddPage: React.FC = () => { onSubmit: (values) => { startTransition(async () => { - const invitePromises = values.users!!.map((value) => - memberService.memberInvite({ - namespaceId: namespaceId!!, - userId: (value.value).id!! - }) - ) + const payload = await memberService.memberBulkInvite({ + namespaceId: namespaceId!!, + userIds: values.users!!.map((value) => (value.value).id!!) + }) - await Promise.all(invitePromises).then(() => { + if ((payload?.errors?.length ?? 0) <= 0) { router.push(`/namespace/${namespaceIndex}/members`) addIslandSuccessNotification({ message: "Added members", }) - }) + } }) } }) From 0ea2f82791a1b2b93177de78e02724a49bf8b1ee Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 16:45:12 +0200 Subject: [PATCH 13/15] feat: add blocked field to user fragments and update mutation for user management --- .../user/components/UserEditDialogComponent.tsx | 14 ++++++++++++++ .../services/fragments/User.basic.fragment.graphql | 1 + .../user/services/fragments/User.fragment.graphql | 1 + .../mutations/User.update.mutation.graphql | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/packages/ce/src/user/components/UserEditDialogComponent.tsx b/src/packages/ce/src/user/components/UserEditDialogComponent.tsx index f14600d1..659f0f51 100644 --- a/src/packages/ce/src/user/components/UserEditDialogComponent.tsx +++ b/src/packages/ce/src/user/components/UserEditDialogComponent.tsx @@ -61,6 +61,7 @@ export const UserEditDialogComponent: React.FC = ( username: user?.username ?? null, readme: user?.readme ?? null, admin: user?.admin ?? false, + blocked: user?.blocked ?? false, password: null, repeatPassword: null, }), [user]) @@ -72,6 +73,7 @@ export const UserEditDialogComponent: React.FC = ( username: string | null, readme: string | null, admin: boolean | null, + blocked: boolean | null, password: string | null, repeatPassword: string | null, }>({ @@ -109,6 +111,7 @@ export const UserEditDialogComponent: React.FC = ( if (values.lastname !== (user?.lastname ?? null)) payload.lastname = values.lastname if (values.readme !== (user?.readme ?? null)) payload.readme = values.readme if (values.admin !== (user?.admin ?? false)) payload.admin = values.admin + if (values.blocked !== (user?.blocked ?? false)) payload.blocked = values.blocked if (values.password) { payload.password = values.password payload.passwordRepeat = values.repeatPassword @@ -272,6 +275,17 @@ export const UserEditDialogComponent: React.FC = ( + + + + Blocked + + Block this user from accessing the application. + + + + + diff --git a/src/packages/ce/src/user/services/fragments/User.basic.fragment.graphql b/src/packages/ce/src/user/services/fragments/User.basic.fragment.graphql index 6f604d28..9c6824dc 100644 --- a/src/packages/ce/src/user/services/fragments/User.basic.fragment.graphql +++ b/src/packages/ce/src/user/services/fragments/User.basic.fragment.graphql @@ -2,6 +2,7 @@ fragment UserBasic on User { __typename admin avatarPath + blocked createdAt readme firstname 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 3aa47f58..4914da4a 100644 --- a/src/packages/ce/src/user/services/fragments/User.fragment.graphql +++ b/src/packages/ce/src/user/services/fragments/User.fragment.graphql @@ -4,6 +4,7 @@ fragment User on User { __typename admin avatarPath + blocked createdAt email emailVerifiedAt diff --git a/src/packages/ce/src/user/services/mutations/User.update.mutation.graphql b/src/packages/ce/src/user/services/mutations/User.update.mutation.graphql index 8bc7c994..c9ff87d3 100644 --- a/src/packages/ce/src/user/services/mutations/User.update.mutation.graphql +++ b/src/packages/ce/src/user/services/mutations/User.update.mutation.graphql @@ -1,5 +1,5 @@ #import "@edition/user/services/fragments/User.basic.fragment.graphql" -mutation update($userId: UserID!, $email: String, $username: String, $firstname: String, $lastname: String, $readme: String, $admin: Boolean, $password: String, $passwordRepeat: String) { +mutation update($userId: UserID!, $email: String, $username: String, $firstname: String, $lastname: String, $readme: String, $admin: Boolean, $blocked: Boolean, $password: String, $passwordRepeat: String) { usersUpdate(input: { userId: $userId email: $email @@ -8,6 +8,7 @@ mutation update($userId: UserID!, $email: String, $username: String, $firstname: lastname: $lastname readme: $readme admin: $admin + blocked: $blocked password: $password passwordRepeat: $passwordRepeat }) { From 75252b78d00bd945a00225ca687d0ac52e062465 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 17:04:36 +0200 Subject: [PATCH 14/15] feat: update project creation redirect to namespace level for improved navigation --- .../ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx b/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx index 4db20c74..03908fe0 100644 --- a/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx +++ b/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx @@ -54,7 +54,7 @@ export const NamespaceProjectsCreatePage: React.FC = () => { namespaceId: namespaceId }).then(payload => { if ((payload?.errors?.length ?? 0) <= 0) { - router.push(`/namespace/${namespaceIndex}/projects`) + router.push(`/namespace/${namespaceIndex}`) } }) }) From 05cd6ed653194779d5df99b1b3c0ffc9ecf35dbe Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 11 Jul 2026 17:05:26 +0200 Subject: [PATCH 15/15] feat: implement user refetching on organization creation for updated navigation --- .../pages/OrganizationCreatePage.tsx | 4 +++- .../ce/src/user/services/User.service.ts | 24 +++++++++++++++++++ .../queries/User.current.query.graphql | 14 +++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/packages/ce/src/user/services/queries/User.current.query.graphql diff --git a/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx b/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx index b3fc2acd..9f11a124 100644 --- a/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx +++ b/src/packages/ce/src/organization/pages/OrganizationCreatePage.tsx @@ -3,12 +3,14 @@ import {Button, Col, Flex, Spacing, Text, TextInput, useForm, useService} from "@code0-tech/pictor"; import Link from "next/link"; import {OrganizationService} from "@edition/organization/services/Organization.service"; +import {UserService} from "@edition/user/services/User.service"; import React from "react"; import {useRouter} from "next/navigation"; export const OrganizationCreatePage = () => { const organizationService = useService(OrganizationService) + const userService = useService(UserService) const [, startTransition] = React.useTransition() const router = useRouter() @@ -29,7 +31,7 @@ export const OrganizationCreatePage = () => { name: values.name as unknown as string }).then(payload => { if ((payload?.errors?.length ?? 0) <= 0) { - router.push("/workspaces") + userService.refetchCurrentUser().then(() => router.push("/workspaces")) } }) }) diff --git a/src/packages/ce/src/user/services/User.service.ts b/src/packages/ce/src/user/services/User.service.ts index 288dbf67..ba5bf797 100644 --- a/src/packages/ce/src/user/services/User.service.ts +++ b/src/packages/ce/src/user/services/User.service.ts @@ -44,6 +44,7 @@ import emailVerificationMutation from "./mutations/User.emailVerification.mutati import passwordResetMutation from "./mutations/User.passwordReset.mutation.graphql" import passwordResetRequestMutation from "./mutations/User.passwordResetRequest.mutation.graphql" import usersQuery from "./queries/Users.query.graphql"; +import currentUserQuery from "./queries/User.current.query.graphql"; import userByUsernameQuery from "./queries/User.byUsername.query.graphql"; import userByIdQuery from "./queries/User.byId.query.graphql"; import {View} from "@code0-tech/pictor/dist/utils/view"; @@ -90,6 +91,29 @@ export class UserService extends ReactiveArrayService { return super.values(); } + async refetchCurrentUser(): Promise { + const result = await this.client.query({ + query: currentUserQuery, + variables: { + firstIdentity: 50, + afterIdentity: null, + firstSession: 50, + afterSession: null, + firstNamespaceMembership: 50, + afterNamespaceMembership: null, + }, + fetchPolicy: "network-only" + }) + + const currentUser = result.data?.currentUser + if (!currentUser) return undefined + + const index = super.values().findIndex(user => user && user.id === currentUser.id) + this.set(index >= 0 ? index : this.i++, new View(currentUser)) + + return currentUser + } + getById(id: User["id"]): User | undefined { const user = super.values().find(user => user && user.id === id) if (user) return user diff --git a/src/packages/ce/src/user/services/queries/User.current.query.graphql b/src/packages/ce/src/user/services/queries/User.current.query.graphql new file mode 100644 index 00000000..33195301 --- /dev/null +++ b/src/packages/ce/src/user/services/queries/User.current.query.graphql @@ -0,0 +1,14 @@ +#import '../fragments/User.fragment.graphql' + +query CurrentUser( + $firstIdentity: Int, + $afterIdentity: String, + $firstSession: Int, + $afterSession: String, + $firstNamespaceMembership: Int, + $afterNamespaceMembership: String +) { + currentUser { + ...User + } +}