Repository: uzh-bf/klicker-uzh
Pull request #4638: enhance: implement logic to leave or delete user groups
Author: login: sjschlapbach
Labels: name: enhancement
name: size:XXL
description: This PR changes 1000+ lines, ignoring generated files.
Created: 2025-04-23T08:09:32Z | Merged: 2025-04-23T11:21:03Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced user group management, allowing users to view, leave, and delete groups with appropriate confirmation dialogs and success/error notifications.
  - Added role badges (owner, admin, member) for user groups.
  - User group lists now display dynamically based on user data.

- **Bug Fixes**
  - None.

- **Documentation**
  - Expanded English and German localization to cover new user group actions and messages.

- **Tests**
  - Added a placeholder for future user group workflow tests.

- **Chores**
  - Removed an unused user group list component.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx b/apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx
new file mode 100644
index 0000000000..009bff54fc
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx
@@ -0,0 +1,27 @@
+import { Toast } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+
+function DeleteUserGroupErrorToast({
+  open,
+  setOpen,
+}: {
+  open: boolean
+  setOpen: (open: boolean) => void
+}) {
+  const t = useTranslations()
+
+  return (
+    <Toast
+      dismissible
+      type="error"
+      openExternal={open}
+      onCloseExternal={() => setOpen(false)}
+      className={{ root: 'max-w-[30rem]' }}
+      duration={10000}
+    >
+      {t('manage.userGroups.deleteGroupError')}
+    </Toast>
+  )
+}
+
+export default DeleteUserGroupErrorToast
diff --git a/apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx b/apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx
new file mode 100644
index 0000000000..99a373e4e7
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx
@@ -0,0 +1,159 @@
+import { useMutation } from '@apollo/client'
+import { faTrashCan } from '@fortawesome/free-regular-svg-icons'
+import { faBan } from '@fortawesome/free-solid-svg-icons'
+import {
+  DeleteUserGroupDocument,
+  GetUserGroupsUserDocument,
+} from '@klicker-uzh/graphql/dist/ops'
+import { Button, Modal } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+import { useEffect, useState } from 'react'
+import ConfirmationItem from '../common/ConfirmationItem'
+import DeleteUserGroupErrorToast from './DeleteUserGroupErrorToast'
+
+function DeleteUserGroupModal({
+  open,
+  onClose,
+  onSuccess,
+  groupId,
+  groupName,
+}: {
+  open: boolean
+  onClose: () => void
+  onSuccess: () => void
+  groupId: number
+  groupName: string
+}) {
+  const t = useTranslations()
+  const [deleteUserGroup] = useMutation(DeleteUserGroupDocument)
+
+  const [errorToast, setErrorToast] = useState(false)
+  const [confirmations, setConfirmations] = useState({
+    resolveGroup: false,
+    revokeDirectPermissions: false,
+    irreversibleAction: false,
+  })
+
+  // on open, reset confirmations
+  useEffect(() => {
+    if (open) {
+      setConfirmations({
+        resolveGroup: false,
+        revokeDirectPermissions: false,
+        irreversibleAction: false,
+      })
+    }
+  }, [open])
+
+  return (
+    <Modal
+      open={open}
+      onClose={onClose}
+      title={t('manage.userGroups.deleteGroup')}
+      className={{ content: '!max-w-2xl' }}
+    >
+      <div className="mb-3">
+        {t('manage.userGroups.confirmDeleteGroup', { groupName })}
+      </div>
+
+      <div className="mb-2 flex flex-col gap-2">
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.resolveGroup}
+          label={t('manage.userGroups.resolveGroupConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              resolveGroup: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-resolve-group-confirm' }}
+        />
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.revokeDirectPermissions}
+          label={t('manage.userGroups.revokeDirectPermissionsConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              revokeDirectPermissions: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-revoke-permissions-confirm' }}
+        />
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.irreversibleAction}
+          label={t('manage.userGroups.irreversibleActionConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              irreversibleAction: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-irrevocable-action-confirm' }}
+        />
+      </div>
+
+      <div className="flex flex-row justify-between">
+        <Button>
+          <Button.Icon icon={faBan} />
+          <Button.Label>{t('shared.generic.cancel')}</Button.Label>
+        </Button>
+        <Button
+          destructive
+          disabled={Object.values(confirmations).some((value) => !value)}
+          onClick={async () => {
+            try {
+              const { data: success } = await deleteUserGroup({
+                variables: {
+                  groupId,
+                },
+                update: (cache, { data }) => {
+                  // check if request was successful
+                  const success = data?.deleteUserGroup
+                  if (!success) return
+                  // update list of answer collections
+                  const catalogObjects = cache.readQuery({
+                    query: GetUserGroupsUserDocument,
+                  })
+                  if (catalogObjects?.getUserGroupsUser) {
+                    cache.writeQuery({
+                      query: GetUserGroupsUserDocument,
+                      data: {
+                        getUserGroupsUser:
+                          catalogObjects?.getUserGroupsUser.filter(
+                            (group) => group.id !== groupId
+                          ),
+                      },
+                    })
+                  }
+                },
+              })
+              if (success?.deleteUserGroup) {
+                onSuccess()
+              } else {
+                setErrorToast(true)
+              }
+            } catch (error) {
+              console.error('Error deleting user group:', error)
+              setErrorToast(true)
+            }
+          }}
+        >
+          <Button.Icon icon={faTrashCan} />
+          <Button.Label>{t('shared.generic.confirm')}</Button.Label>
+        </Button>
+      </div>
+      <DeleteUserGroupErrorToast
+        open={errorToast}
+        setOpen={() => setErrorToast(false)}
+      />
+    </Modal>
+  )
+}
+
+export default DeleteUserGroupModal
diff --git a/apps/frontend-manage/src/components/groups/DeleteUserGroupSuccessToast.tsx b/apps/frontend-manage/src/components/groups/DeleteUserGroupSuccessToast.tsx
new file mode 100644
index 0000000000..a50d0afb54
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/DeleteUserGroupSuccessToast.tsx
@@ -0,0 +1,27 @@
+import { Toast } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+
+function DeleteUserGroupSuccessToast({
+  open,
+  setOpen,
+}: {
+  open: boolean
+  setOpen: (open: boolean) => void
+}) {
+  const t = useTranslations()
+
+  return (
+    <Toast
+      dismissible
+      type="success"
+      openExternal={open}
+      onCloseExternal={() => setOpen(false)}
+      className={{ root: 'max-w-[30rem]' }}
+      duration={3000}
+    >
+      {t('manage.userGroups.deleteGroupSuccess')}
+    </Toast>
+  )
+}
+
+export default DeleteUserGroupSuccessToast
diff --git a/apps/frontend-manage/src/components/groups/LeaveUserGroupErrorToast.tsx b/apps/frontend-manage/src/components/groups/LeaveUserGroupErrorToast.tsx
new file mode 100644
index 0000000000..4beb675fa1
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/LeaveUserGroupErrorToast.tsx
@@ -0,0 +1,27 @@
+import { Toast } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+
+function LeaveUserGroupErrorToast({
+  open,
+  setOpen,
+}: {
+  open: boolean
+  setOpen: (open: boolean) => void
+}) {
+  const t = useTranslations()
+
+  return (
+    <Toast
+      dismissible
+      type="error"
+      openExternal={open}
+      onCloseExternal={() => setOpen(false)}
+      className={{ root: 'max-w-[30rem]' }}
+      duration={10000}
+    >
+      {t('manage.userGroups.leaveGroupError')}
+    </Toast>
+  )
+}
+
+export default LeaveUserGroupErrorToast
diff --git a/apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx b/apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx
new file mode 100644
index 0000000000..dc94053372
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx
@@ -0,0 +1,103 @@
+import { useMutation } from '@apollo/client'
+import {
+  faBan,
+  faPersonWalkingArrowRight,
+} from '@fortawesome/free-solid-svg-icons'
+import {
+  GetUserGroupsUserDocument,
+  LeaveUserGroupDocument,
+} from '@klicker-uzh/graphql/dist/ops'
+import { Button, Modal } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+import { useState } from 'react'
+import LeaveUserGroupErrorToast from './LeaveUserGroupErrorToast'
+
+function LeaveUserGroupModal({
+  open,
+  onClose,
+  onSuccess,
+  groupId,
+  groupName,
+}: {
+  open: boolean
+  onClose: () => void
+  onSuccess: () => void
+  groupId: number
+  groupName: string
+}) {
+  const t = useTranslations()
+  const [errorToast, setErrorToast] = useState(false)
+  const [leaveUserGroup] = useMutation(LeaveUserGroupDocument)
+
+  return (
+    <Modal
+      open={open}
+      onClose={onClose}
+      title={t('manage.userGroups.leaveGroup')}
+      className={{ content: '!max-w-xl' }}
+    >
+      <div className="mb-3">
+        {t('manage.userGroups.confirmLeaveGroup', { groupName })}
+      </div>
+      <div className="flex flex-row justify-between">
+        <Button>
+          <Button.Icon icon={faBan} />
+          <Button.Label>{t('shared.generic.cancel')}</Button.Label>
+        </Button>
+        <Button
+          destructive
+          onClick={async () => {
+            try {
+              const { data: success } = await leaveUserGroup({
+                variables: {
+                  groupId,
+                },
+                update: (cache, { data }) => {
+                  // check if request was successful
+                  const success = data?.leaveUserGroup
+                  if (!success) return
+
+                  // update list of answer collections
+                  const catalogObjects = cache.readQuery({
+                    query: GetUserGroupsUserDocument,
+                  })
+
+                  if (catalogObjects?.getUserGroupsUser) {
+                    cache.writeQuery({
+                      query: GetUserGroupsUserDocument,
+
+                      data: {
+                        getUserGroupsUser:
+                          catalogObjects?.getUserGroupsUser.filter(
+                            (group) => group.id !== groupId
+                          ),
+                      },
+                    })
+                  }
+                },
+              })
+
+              if (success?.leaveUserGroup) {
+                onSuccess()
+              } else {
+                setErrorToast(true)
+              }
+            } catch (error) {
+              console.error('Error leaving user group:', error)
+              setErrorToast(true)
+            }
+          }}
+        >
+          <Button.Icon icon={faPersonWalkingArrowRight} />
+          <Button.Label>{t('shared.generic.confirm')}</Button.Label>
+        </Button>
+      </div>
+      <LeaveUserGroupErrorToast
+        open={errorToast}
+        setOpen={() => setErrorToast(false)}
+      />
+    </Modal>
+  )
+}
+
+export default LeaveUserGroupModal
diff --git a/apps/frontend-manage/src/components/groups/LeaveUserGroupSuccessToast.tsx b/apps/frontend-manage/src/components/groups/LeaveUserGroupSuccessToast.tsx
new file mode 100644
index 0000000000..b869954c6e
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/LeaveUserGroupSuccessToast.tsx
@@ -0,0 +1,27 @@
+import { Toast } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+
+function LeaveUserGroupSuccessToast({
+  open,
+  setOpen,
+}: {
+  open: boolean
+  setOpen: (open: boolean) => void
+}) {
+  const t = useTranslations()
+
+  return (
+    <Toast
+      dismissible
+      type="success"
+      openExternal={open}
+      onCloseExternal={() => setOpen(false)}
+      className={{ root: 'max-w-[30rem]' }}
+      duration={3000}
+    >
+      {t('manage.userGroups.leaveGroupSuccess')}
+    </Toast>
+  )
+}
+
+export default LeaveUserGroupSuccessToast
diff --git a/apps/frontend-manage/src/components/groups/UserGroupBadge.tsx b/apps/frontend-manage/src/components/groups/UserGroupBadge.tsx
new file mode 100644
index 0000000000..80c4ad2859
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/UserGroupBadge.tsx
@@ -0,0 +1,46 @@
+import { faPencil, faUserTie } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { Badge } from '@uzh-bf/design-system/dist/future'
+import { useTranslations } from 'next-intl'
+
+function UserGroupBadge({
+  isMember,
+  isAdmin,
+  isOwner,
+}: {
+  isMember: boolean
+  isAdmin: boolean
+  isOwner: boolean
+}) {
+  const t = useTranslations()
+
+  if (isOwner) {
+    return (
+      <Badge className="flex flex-row items-center gap-1.5 border-2 border-green-600 bg-green-50 text-black hover:bg-green-100">
+        <FontAwesomeIcon icon={faUserTie} />
+        <div>{t('shared.generic.owner')}</div>
+      </Badge>
+    )
+  }
+
+  if (isAdmin) {
+    return (
+      <Badge className="flex flex-row items-center gap-1.5 border-2 border-orange-600 bg-orange-50 text-black hover:bg-orange-100">
+        <FontAwesomeIcon icon={faPencil} />
+        <div>{t('manage.userGroups.admin')}</div>
+      </Badge>
+    )
+  }
+
+  if (isMember) {
+    return (
+      <Badge className="border-2 border-gray-600 bg-gray-50 text-black hover:bg-gray-100">
+        <div>{t('manage.userGroups.member')}</div>
+      </Badge>
+    )
+  }
+
+  return null
+}
+
+export default UserGroupBadge
diff --git a/apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx b/apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx
index c971ed8edf..b1e1d1220f 100644
--- a/apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx
+++ b/apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx
@@ -5,7 +5,10 @@ import {
   faPlusCircle,
   faTrashCan,
 } from '@fortawesome/free-solid-svg-icons'
-import { CreateUserGroupDocument } from '@klicker-uzh/graphql/dist/ops'
+import {
+  CreateUserGroupDocument,
+  GetUserGroupsUserDocument,
+} from '@klicker-uzh/graphql/dist/ops'
 import {
   Button,
   FormikSwitchField,
@@ -71,23 +74,23 @@ function UserGroupCreationForm({
                 name: values.name!,
                 members: values.members!,
               },
-              // update: (cache, { data }) => {
-              //   if (!data?.createAnswerCollection) return
-              //   const queryData = cache.readQuery({
-              //     query: GetAnswerCollectionsInfoDocument,
-              //   })
-              //   const previousCollections = queryData?.getAnswerCollectionsInfo
-              //   if (!previousCollections) return
-              //   cache.writeQuery({
-              //     query: GetAnswerCollectionsInfoDocument,
-              //     data: {
-              //       getAnswerCollectionsInfo: [
-              //         ...previousCollections,
-              //         data.createAnswerCollection,
-              //       ],
-              //     },
-              //   })
-              // },
+              update: (cache, { data }) => {
+                if (!data?.createUserGroup) return
+                const queryData = cache.readQuery({
+                  query: GetUserGroupsUserDocument,
+                })
+                const previousGroups = queryData?.getUserGroupsUser
+                if (!previousGroups) return
+                cache.writeQuery({
+                  query: GetUserGroupsUserDocument,
+                  data: {
+                    getUserGroupsUser: [
+                      ...previousGroups,
+                      data.createUserGroup,
+                    ],
+                  },
+                })
+              },
             })
 
             if (data?.createUserGroup?.id) {
diff --git a/apps/frontend-manage/src/components/groups/UserGroupEntry.tsx b/apps/frontend-manage/src/components/groups/UserGroupEntry.tsx
new file mode 100644
index 0000000000..71c3f1966b
--- /dev/null
+++ b/apps/frontend-manage/src/components/groups/UserGroupEntry.tsx
@@ -0,0 +1,159 @@
+import { faEye, faTrashCan } from '@fortawesome/free-regular-svg-icons'
+import {
+  faEllipsisVertical,
+  faPencil,
+  faPersonWalkingArrowRight,
+} from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { UserGroup } from '@klicker-uzh/graphql/dist/ops'
+import { Button, Dropdown } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+import { useState } from 'react'
+import DeleteUserGroupModal from './DeleteUserGroupModal'
+import DeleteUserGroupSuccessToast from './DeleteUserGroupSuccessToast'
+import LeaveUserGroupModal from './LeaveUserGroupModal'
+import LeaveUserGroupSuccessToast from './LeaveUserGroupSuccessToast'
+import UserGroupBadge from './UserGroupBadge'
+
+function UserGroupEntry({ group }: { group: UserGroup }) {
+  const t = useTranslations()
+
+  const [leaveGroupModal, setLeaveGroupModal] = useState(false)
+  const [deleteGroupModal, setDeleteGroupModal] = useState(false)
+  const [editModal, setEditModal] = useState(false)
+
+  const [leaveSuccess, setLeaveSuccess] = useState(false)
+  const [deleteSuccess, setDeleteSuccess] = useState(false)
+
+  // required functionalities (ideally incl. audit log entries):
+  // TODO: - promote member to ADMIN
+  // TODO: - demote ADMIN to MEMBER
+  // TODO: - remove member from group
+  // TODO: - add member to the group
+  // TODO: - edit group name
+  // TODO: - transfer ownership
+  // TODO: - leave group - only possible for non-owner - ask to confirm in removal modal
+  // TODO: - delete group - only possible for owner - ask to confirm in deletion modal
+
+  return (
+    <>
+      <div
+        data-cy={`user-group-${group.name}`}
+        className="flex flex-row justify-between border-b-2 border-gray-300 px-1.5 py-1.5 last:border-b-0"
+      >
+        <div>
+          <div className="flex flex-row items-center gap-4">
+            <div>{group.name}</div>
+          </div>
+          <div className="text-sm text-gray-500">{`${group.numOfMembers} ${t('manage.userGroups.members')}`}</div>
+        </div>
+        <div className="flex flex-row items-center gap-3">
+          <UserGroupBadge
+            isMember={group.isMember ?? false}
+            isAdmin={group.isAdmin ?? false}
+            isOwner={group.isOwner ?? false}
+          />
+          <Dropdown
+            items={[
+              {
+                label: (
+                  <div className="flex cursor-pointer items-center rounded px-1.5 py-0.5 hover:bg-gray-100">
+                    <FontAwesomeIcon
+                      icon={group.isMember ? faEye : faPencil}
+                      className="mr-2.5 h-4 w-4"
+                    />
+                    {group.isMember
+                      ? t('manage.userGroups.viewGroup')
+                      : t('manage.userGroups.editGroup')}
+                  </div>
+                ),
+                onClick: () => setEditModal(true),
+                data: { cy: `leave-group-${group.name}` },
+                className: { item: 'text-red-500' },
+              },
+              ...(!group.isOwner
+                ? [
+                    {
+                      label: (
+                        <div className="flex cursor-pointer items-center rounded px-1.5 py-0.5 text-red-600 hover:bg-gray-100">
+                          <FontAwesomeIcon
+                            icon={faPersonWalkingArrowRight}
+                            className="mr-2.5 h-4 w-4"
+                          />
+                          {t('manage.userGroups.leaveGroup')}
+                        </div>
+                      ),
+                      onClick: () => setLeaveGroupModal(true),
+                      data: { cy: `leave-group-${group.name}` },
+                    },
+                  ]
+                : []),
+              ...(group.isOwner
+                ? [
+                    {
+                      label: (
+                        <div className="flex cursor-pointer items-center rounded px-1.5 py-0.5 text-red-600 hover:bg-gray-100">
+                          <FontAwesomeIcon
+                            icon={faTrashCan}
+                            className="mr-2.5 h-4 w-4"
+                          />
+                          {t('manage.userGroups.deleteGroup')}
+                        </div>
+                      ),
+                      onClick: () => setDeleteGroupModal(true),
+                      data: { cy: `delete-group-${group.name}` },
+                    },
+                  ]
+                : []),
+            ]}
+            trigger={
+              <Button
+                basic
+                className={{
+                  root: 'rounded-full p-1.5 text-gray-500 hover:bg-gray-100',
+                }}
+              >
+                <Button.Icon withoutLabel icon={faEllipsisVertical} />
+              </Button>
+            }
+            data={{ cy: `user-group-actions-${group.name}` }}
+          />
+        </div>
+      </div>
+
+      <LeaveUserGroupModal
+        open={leaveGroupModal}
+        onClose={() => setLeaveGroupModal(false)}
+        onSuccess={() => {
+          setLeaveGroupModal(false)
+          setLeaveSuccess(true)
+        }}
+        groupId={group.id}
+        groupName={group.name}
+      />
+      <LeaveUserGroupSuccessToast
+        open={leaveSuccess}
+        setOpen={() => setLeaveSuccess(false)}
+      />
+
+      <DeleteUserGroupModal
+        open={deleteGroupModal}
+        onClose={() => setDeleteGroupModal(false)}
+        groupId={group.id}
+        groupName={group.name}
+        onSuccess={() => {
+          setDeleteGroupModal(false)
+          setDeleteSuccess(true)
+        }}
+      />
+      <DeleteUserGroupSuccessToast
+        open={deleteSuccess}
+        setOpen={() => setDeleteSuccess(false)}
+      />
+
+      {/* // TODO: Edit modal with all remaining functionalities */}
+    </>
+  )
+}
+
+export default UserGroupEntry
diff --git a/apps/frontend-manage/src/components/groups/UserGroupList.tsx b/apps/frontend-manage/src/components/groups/UserGroupList.tsx
deleted file mode 100644
index 56ea6e5d9b..0000000000
--- a/apps/frontend-manage/src/components/groups/UserGroupList.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-function UserGroupList() {
-  return <div>USER GROUP LIST</div>
-}
-
-export default UserGroupList
diff --git a/apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx b/apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx
index 694f376b03..8bb09a5af3 100644
--- a/apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx
+++ b/apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx
@@ -1,10 +1,14 @@
+import { useQuery } from '@apollo/client'
+import { GetUserGroupsUserDocument } from '@klicker-uzh/graphql/dist/ops'
+import Loader from '@klicker-uzh/shared-components/src/Loader'
 import { H2 } from '@uzh-bf/design-system'
 import { useTranslations } from 'next-intl'
 import UserGroupCreation from './UserGroupCreation'
-import UserGroupList from './UserGroupList'
+import UserGroupEntry from './UserGroupEntry'
 
 function UserGroupsManagement() {
   const t = useTranslations()
+  const { data, loading } = useQuery(GetUserGroupsUserDocument)
 
   return (
     <div>
@@ -16,7 +20,13 @@ function UserGroupsManagement() {
             <UserGroupCreation />
           </div>
           <div className="lg:w-1/2 lg:pr-4">
-            <UserGroupList />
+            {loading ? (
+              <Loader />
+            ) : (
+              data?.getUserGroupsUser?.map((group) => (
+                <UserGroupEntry key={`group-item-${group.id}`} group={group} />
+              ))
+            )}
           </div>
         </div>
       </div>
diff --git a/cypress/cypress/e2e/L-catalog-workflow.cy.ts b/cypress/cypress/e2e/L-catalog-workflow.cy.ts
index 42c6ec0cd2..befedd1222 100644
--- a/cypress/cypress/e2e/L-catalog-workflow.cy.ts
+++ b/cypress/cypress/e2e/L-catalog-workflow.cy.ts
@@ -806,6 +806,11 @@ describe('Test all functionalities of catalog collections and objects contained
   })
   // #endregion
 
+  // ! Part 4: User Groups
+  // #region
+  // TODO: test the creation of a user group and that it is shown in the UI
+  // #endregion
+
   // ! Cleanup
   // #region
   it('Cleanup: Remove the shared answer collection from all accounts and delete it', function () {
diff --git a/packages/graphql/src/graphql/ops/MCreateUserGroup.graphql b/packages/graphql/src/graphql/ops/MCreateUserGroup.graphql
index 57723bb4a6..f8e081bf98 100644
--- a/packages/graphql/src/graphql/ops/MCreateUserGroup.graphql
+++ b/packages/graphql/src/graphql/ops/MCreateUserGroup.graphql
@@ -1,6 +1,5 @@
 mutation CreateUserGroup($name: String!, $members: [UserGroupMembersInput!]!) {
   createUserGroup(name: $name, members: $members) {
-    __typename
     id
     name
     members {
@@ -13,5 +12,9 @@ mutation CreateUserGroup($name: String!, $members: [UserGroupMembersInput!]!) {
       shortname
       email
     }
+    numOfMembers
+    isMember
+    isAdmin
+    isOwner
   }
 }
diff --git a/packages/graphql/src/graphql/ops/MDeleteUserGroup.graphql b/packages/graphql/src/graphql/ops/MDeleteUserGroup.graphql
new file mode 100644
index 0000000000..3efc7a28fe
--- /dev/null
+++ b/packages/graphql/src/graphql/ops/MDeleteUserGroup.graphql
@@ -0,0 +1,3 @@
+mutation DeleteUserGroup($groupId: Int!) {
+  deleteUserGroup(groupId: $groupId)
+}
diff --git a/packages/graphql/src/graphql/ops/MLeaveUserGroup.graphql b/packages/graphql/src/graphql/ops/MLeaveUserGroup.graphql
new file mode 100644
index 0000000000..f65ae82676
--- /dev/null
+++ b/packages/graphql/src/graphql/ops/MLeaveUserGroup.graphql
@@ -0,0 +1,3 @@
+mutation LeaveUserGroup($groupId: Int!) {
+  leaveUserGroup(groupId: $groupId)
+}
diff --git a/packages/graphql/src/graphql/ops/QGetUserGroupsUser.graphql b/packages/graphql/src/graphql/ops/QGetUserGroupsUser.graphql
new file mode 100644
index 0000000000..2aa98a457d
--- /dev/null
+++ b/packages/graphql/src/graphql/ops/QGetUserGroupsUser.graphql
@@ -0,0 +1,20 @@
+query GetUserGroupsUser {
+  getUserGroupsUser {
+    id
+    name
+    members {
+      id
+      shortname
+      email
+    }
+    admins {
+      id
+      shortname
+      email
+    }
+    numOfMembers
+    isMember
+    isAdmin
+    isOwner
+  }
+}
diff --git a/packages/graphql/src/ops.schema.json b/packages/graphql/src/ops.schema.json
index b0a44d03dc..722681d5d3 100644
--- a/packages/graphql/src/ops.schema.json
+++ b/packages/graphql/src/ops.schema.json
@@ -18408,6 +18408,39 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "deleteUserGroup",
+            "description": null,
+            "args": [
+              {
+                "name": "groupId",
+                "description": null,
+                "type": {
+                  "kind": "NON_NULL",
+                  "name": null,
+                  "ofType": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  }
+                },
+                "defaultValue": null,
+                "isDeprecated": false,
+                "deprecationReason": null
+              }
+            ],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "SCALAR",
+                "name": "Boolean",
+                "ofType": null
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "deleteUserLogin",
             "description": null,
@@ -20171,6 +20204,39 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "leaveUserGroup",
+            "description": null,
+            "args": [
+              {
+                "name": "groupId",
+                "description": null,
+                "type": {
+                  "kind": "NON_NULL",
+                  "name": null,
+                  "ofType": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  }
+                },
+                "defaultValue": null,
+                "isDeprecated": false,
+                "deprecationReason": null
+              }
+            ],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "SCALAR",
+                "name": "Boolean",
+                "ofType": null
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "loginParticipant",
             "description": null,
@@ -29336,6 +29402,26 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "getUserGroupsUser",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "LIST",
+              "name": null,
+              "ofType": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "OBJECT",
+                  "name": "UserGroup",
+                  "ofType": null
+                }
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "getUsersPrivatePreview",
             "description": null,
@@ -33981,6 +34067,42 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "isAdmin",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "SCALAR",
+              "name": "Boolean",
+              "ofType": null
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
+          {
+            "name": "isMember",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "SCALAR",
+              "name": "Boolean",
+              "ofType": null
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
+          {
+            "name": "isOwner",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "SCALAR",
+              "name": "Boolean",
+              "ofType": null
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "members",
             "description": null,
diff --git a/packages/graphql/src/ops.ts b/packages/graphql/src/ops.ts
index f6c3ee79dd..8e6623ebda 100644
--- a/packages/graphql/src/ops.ts
+++ b/packages/graphql/src/ops.ts
@@ -1526,6 +1526,7 @@ export type Mutation = {
   deletePracticeQuiz?: Maybe<PracticeQuiz>;
   deleteQuestion?: Maybe<Element>;
   deleteTag?: Maybe<Tag>;
+  deleteUserGroup: Scalars['Boolean']['output'];
   deleteUserLogin?: Maybe<UserLogin>;
   editActivityTemplate: Scalars['Boolean']['output'];
   editAnswerCollectionEntry?: Maybe<AnswerCollectionEntry>;
@@ -1556,6 +1557,7 @@ export type Mutation = {
   leaveCourseLeaderboard?: Maybe<LeaveCourseParticipation>;
   leaveParticipantGroup?: Maybe<ParticipantGroup>;
   leaveRandomCourseGroupPool: Scalars['Boolean']['output'];
+  leaveUserGroup: Scalars['Boolean']['output'];
   loginParticipant?: Maybe<Scalars['ID']['output']>;
   loginParticipantMagicLink?: Maybe<Scalars['ID']['output']>;
   loginParticipantWithLti?: Maybe<ParticipantTokenData>;
@@ -1971,6 +1973,11 @@ export type MutationDeleteTagArgs = {
 };
 
 
+export type MutationDeleteUserGroupArgs = {
+  groupId: Scalars['Int']['input'];
+};
+
+
 export type MutationDeleteUserLoginArgs = {
   id: Scalars['String']['input'];
 };
@@ -2164,6 +2171,11 @@ export type MutationLeaveRandomCourseGroupPoolArgs = {
 };
 
 
+export type MutationLeaveUserGroupArgs = {
+  groupId: Scalars['Int']['input'];
+};
+
+
 export type MutationLoginParticipantArgs = {
   password: Scalars['String']['input'];
   usernameOrEmail: Scalars['String']['input'];
@@ -2974,6 +2986,7 @@ export type Query = {
   getStudentCourseLeaderboard?: Maybe<StudentCourseLeaderboard>;
   getTemplateInformation?: Maybe<ActivityTemplateMetadata>;
   getTemplatePreviewAnswerCollectionEntries?: Maybe<Array<AnswerCollectionPreviewEntry>>;
+  getUserGroupsUser?: Maybe<Array<UserGroup>>;
   getUsersPrivatePreview?: Maybe<Array<UserInfo>>;
   groupActivities?: Maybe<Array<GroupActivity>>;
   groupActivity?: Maybe<GroupActivity>;
@@ -3801,6 +3814,9 @@ export type UserGroup = {
   __typename?: 'UserGroup';
   admins?: Maybe<Array<UserInfo>>;
   id: Scalars['Int']['output'];
+  isAdmin?: Maybe<Scalars['Boolean']['output']>;
+  isMember?: Maybe<Scalars['Boolean']['output']>;
+  isOwner?: Maybe<Scalars['Boolean']['output']>;
   members?: Maybe<Array<UserInfo>>;
   name: Scalars['String']['output'];
   numOfMembers?: Maybe<Scalars['Int']['output']>;
@@ -4191,7 +4207,7 @@ export type CreateUserGroupMutationVariables = Exact<{
 }>;
 
 
-export type CreateUserGroupMutation = { __typename?: 'Mutation', createUserGroup?: { __typename: 'UserGroup', id: number, name: string, members?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null, admins?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null } | null };
+export type CreateUserGroupMutation = { __typename?: 'Mutation', createUserGroup?: { __typename?: 'UserGroup', id: number, name: string, numOfMembers?: number | null, isMember?: boolean | null, isAdmin?: boolean | null, isOwner?: boolean | null, members?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null, admins?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null } | null };
 
 export type CreateUserLoginMutationVariables = Exact<{
   password: Scalars['String']['input'];
@@ -4318,6 +4334,13 @@ export type DeleteTagMutationVariables = Exact<{
 
 export type DeleteTagMutation = { __typename?: 'Mutation', deleteTag?: { __typename?: 'Tag', id: number } | null };
 
+export type DeleteUserGroupMutationVariables = Exact<{
+  groupId: Scalars['Int']['input'];
+}>;
+
+
+export type DeleteUserGroupMutation = { __typename?: 'Mutation', deleteUserGroup: boolean };
+
 export type DeleteUserLoginMutationVariables = Exact<{
   id: Scalars['String']['input'];
 }>;
@@ -4580,6 +4603,13 @@ export type LeaveRandomCourseGroupPoolMutationVariables = Exact<{
 
 export type LeaveRandomCourseGroupPoolMutation = { __typename?: 'Mutation', leaveRandomCourseGroupPool: boolean };
 
+export type LeaveUserGroupMutationVariables = Exact<{
+  groupId: Scalars['Int']['input'];
+}>;
+
+
+export type LeaveUserGroupMutation = { __typename?: 'Mutation', leaveUserGroup: boolean };
+
 export type LoginParticipantMutationVariables = Exact<{
   usernameOrEmail: Scalars['String']['input'];
   password: Scalars['String']['input'];
@@ -5681,6 +5711,11 @@ export type GetUserCoursesQueryVariables = Exact<{ [key: string]: never; }>;
 
 export type GetUserCoursesQuery = { __typename?: 'Query', userCourses?: Array<{ __typename?: 'Course', id: string, name: string, displayName: string, color: string, pinCode?: number | null, isArchived: boolean, isGamificationEnabled: boolean, isGroupCreationEnabled: boolean, description?: string | null, startDate: any, endDate: any, createdAt?: any | null, updatedAt?: any | null }> | null };
 
+export type GetUserGroupsUserQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type GetUserGroupsUserQuery = { __typename?: 'Query', getUserGroupsUser?: Array<{ __typename?: 'UserGroup', id: number, name: string, numOfMembers?: number | null, isMember?: boolean | null, isAdmin?: boolean | null, isOwner?: boolean | null, members?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null, admins?: Array<{ __typename?: 'UserInfo', id?: string | null, shortname: string, email: string }> | null }> | null };
+
 export type GetUserLiveQuizzesQueryVariables = Exact<{ [key: string]: never; }>;
 
 
@@ -5866,7 +5901,7 @@ export const CreateMicroLearningDocument = {"kind":"Document","definitions":[{"k
 export const CreateParticipantAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateParticipantAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isProfilePublic"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signedLtiData"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createParticipantAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}},{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}},{"kind":"Argument","name":{"kind":"Name","value":"isProfilePublic"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isProfilePublic"}}},{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}},{"kind":"Argument","name":{"kind":"Name","value":"signedLtiData"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signedLtiData"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"participantToken"}},{"kind":"Field","name":{"kind":"Name","value":"participant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]} as unknown as DocumentNode<CreateParticipantAccountMutation, CreateParticipantAccountMutationVariables>;
 export const CreateParticipantGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateParticipantGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createParticipantGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]} as unknown as DocumentNode<CreateParticipantGroupMutation, CreateParticipantGroupMutationVariables>;
 export const CreatePracticeQuizDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePracticeQuiz"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"displayName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"description"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stacks"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ElementStackInput"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"multiplier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"order"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ElementOrderType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"resetTimeDays"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPracticeQuiz"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"displayName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"displayName"}}},{"kind":"Argument","name":{"kind":"Name","value":"description"},"value":{"kind":"Variable","name":{"kind":"Name","value":"description"}}},{"kind":"Argument","name":{"kind":"Name","value":"stacks"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stacks"}}},{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}},{"kind":"Argument","name":{"kind":"Name","value":"multiplier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"multiplier"}}},{"kind":"Argument","name":{"kind":"Name","value":"order"},"value":{"kind":"Variable","name":{"kind":"Name","value":"order"}}},{"kind":"Argument","name":{"kind":"Name","value":"resetTimeDays"},"value":{"kind":"Variable","name":{"kind":"Name","value":"resetTimeDays"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"resetTimeDays"}},{"kind":"Field","name":{"kind":"Name","value":"orderType"}},{"kind":"Field","name":{"kind":"Name","value":"course"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"stacks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"elements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ElementData"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ElementData"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ElementInstance"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"elementData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ChoicesElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"hasAnswerFeedbacks"}},{"kind":"Field","name":{"kind":"Name","value":"displayMode"}},{"kind":"Field","name":{"kind":"Name","value":"choices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ix"}},{"kind":"Field","name":{"kind":"Name","value":"correct"}},{"kind":"Field","name":{"kind":"Name","value":"feedback"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NumericalElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"accuracy"}},{"kind":"Field","name":{"kind":"Name","value":"placeholder"}},{"kind":"Field","name":{"kind":"Name","value":"unit"}},{"kind":"Field","name":{"kind":"Name","value":"restrictions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}},{"kind":"Field","name":{"kind":"Name","value":"solutionRanges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exactSolutions"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FreeTextElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"restrictions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"maxLength"}}]}},{"kind":"Field","name":{"kind":"Name","value":"solutions"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SelectionElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfInputs"}},{"kind":"Field","name":{"kind":"Name","value":"answerCollection"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"entries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"answerCollectionSolutionIds"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CaseStudyElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"answerCollectionId"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}},{"kind":"Field","name":{"kind":"Name","value":"step"}},{"kind":"Field","name":{"kind":"Name","value":"unit"}},{"kind":"Field","name":{"kind":"Name","value":"labels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"mid"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"cases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"solutions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"itemId"}},{"kind":"Field","name":{"kind":"Name","value":"criteriaSolutions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"criterionId"}},{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FlashcardElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ContentElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}}]}}]}}]}}]} as unknown as DocumentNode<CreatePracticeQuizMutation, CreatePracticeQuizMutationVariables>;
-export const CreateUserGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUserGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"members"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserGroupMembersInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUserGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"members"},"value":{"kind":"Variable","name":{"kind":"Name","value":"members"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserGroupMutation, CreateUserGroupMutationVariables>;
+export const CreateUserGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUserGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"members"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserGroupMembersInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUserGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"members"},"value":{"kind":"Variable","name":{"kind":"Name","value":"members"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"numOfMembers"}},{"kind":"Field","name":{"kind":"Name","value":"isMember"}},{"kind":"Field","name":{"kind":"Name","value":"isAdmin"}},{"kind":"Field","name":{"kind":"Name","value":"isOwner"}}]}}]}}]} as unknown as DocumentNode<CreateUserGroupMutation, CreateUserGroupMutationVariables>;
 export const CreateUserLoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUserLogin"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"scope"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserLoginScope"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUserLogin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"scope"},"value":{"kind":"Variable","name":{"kind":"Name","value":"scope"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserLoginMutation, CreateUserLoginMutationVariables>;
 export const DeactivateLiveQuizBlockDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeactivateLiveQuizBlock"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"quizId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"blockId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deactivateLiveQuizBlock"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"quizId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"quizId"}}},{"kind":"Argument","name":{"kind":"Name","value":"blockId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"blockId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"activeBlock"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"blocks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode<DeactivateLiveQuizBlockMutation, DeactivateLiveQuizBlockMutationVariables>;
 export const DeclineObjectSharingRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeclineObjectSharingRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"declineObjectSharingRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"requestId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}}},{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}]}]}}]} as unknown as DocumentNode<DeclineObjectSharingRequestMutation, DeclineObjectSharingRequestMutationVariables>;
@@ -5884,6 +5919,7 @@ export const DeleteParticipantAccountDocument = {"kind":"Document","definitions"
 export const DeletePracticeQuizDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePracticeQuiz"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePracticeQuiz"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeletePracticeQuizMutation, DeletePracticeQuizMutationVariables>;
 export const DeleteQuestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteQuestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteQuestion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ChoicesElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NumericalElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FreeTextElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SelectionElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CaseStudyElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FlashcardElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ContentElement"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteQuestionMutation, DeleteQuestionMutationVariables>;
 export const DeleteTagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteTag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteTag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteTagMutation, DeleteTagMutationVariables>;
+export const DeleteUserGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUserGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUserGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"groupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}}}]}]}}]} as unknown as DocumentNode<DeleteUserGroupMutation, DeleteUserGroupMutationVariables>;
 export const DeleteUserLoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUserLogin"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUserLogin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteUserLoginMutation, DeleteUserLoginMutationVariables>;
 export const EditActivityTemplateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditActivityTemplate"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"activityId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"activityType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ActivityType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"description"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"instructions"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"editActivityTemplate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"activityId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"activityId"}}},{"kind":"Argument","name":{"kind":"Name","value":"activityType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"activityType"}}},{"kind":"Argument","name":{"kind":"Name","value":"templateId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"description"},"value":{"kind":"Variable","name":{"kind":"Name","value":"description"}}},{"kind":"Argument","name":{"kind":"Name","value":"instructions"},"value":{"kind":"Variable","name":{"kind":"Name","value":"instructions"}}}]}]}}]} as unknown as DocumentNode<EditActivityTemplateMutation, EditActivityTemplateMutationVariables>;
 export const EditAnswerCollectionEntryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditAnswerCollectionEntry"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"collectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"editAnswerCollectionEntry"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}},{"kind":"Argument","name":{"kind":"Name","value":"collectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"collectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]} as unknown as DocumentNode<EditAnswerCollectionEntryMutation, EditAnswerCollectionEntryMutationVariables>;
@@ -5914,6 +5950,7 @@ export const JoinRandomCourseGroupPoolDocument = {"kind":"Document","definitions
 export const LeaveCourseLeaderboardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveCourseLeaderboard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leaveCourseLeaderboard"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"participation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}}]}}]}}]}}]} as unknown as DocumentNode<LeaveCourseLeaderboardMutation, LeaveCourseLeaderboardMutationVariables>;
 export const LeaveParticipantGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveParticipantGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leaveParticipantGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"groupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}}},{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]} as unknown as DocumentNode<LeaveParticipantGroupMutation, LeaveParticipantGroupMutationVariables>;
 export const LeaveRandomCourseGroupPoolDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveRandomCourseGroupPool"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leaveRandomCourseGroupPool"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}]}]}}]} as unknown as DocumentNode<LeaveRandomCourseGroupPoolMutation, LeaveRandomCourseGroupPoolMutationVariables>;
+export const LeaveUserGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveUserGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leaveUserGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"groupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}}}]}]}}]} as unknown as DocumentNode<LeaveUserGroupMutation, LeaveUserGroupMutationVariables>;
 export const LoginParticipantDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LoginParticipant"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"usernameOrEmail"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginParticipant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"usernameOrEmail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"usernameOrEmail"}}},{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}]}]}}]} as unknown as DocumentNode<LoginParticipantMutation, LoginParticipantMutationVariables>;
 export const LoginParticipantMagicLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LoginParticipantMagicLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginParticipantMagicLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode<LoginParticipantMagicLinkMutation, LoginParticipantMagicLinkMutationVariables>;
 export const LoginParticipantWithLtiDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LoginParticipantWithLti"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signedLtiData"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginParticipantWithLti"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"signedLtiData"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signedLtiData"}}},{"kind":"Argument","name":{"kind":"Name","value":"courseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"participant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"participantToken"}}]}}]}}]} as unknown as DocumentNode<LoginParticipantWithLtiMutation, LoginParticipantWithLtiMutationVariables>;
@@ -6058,6 +6095,7 @@ export const GetTemplateInformationDocument = {"kind":"Document","definitions":[
 export const GetTemplatePreviewAnswerCollectionEntriesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTemplatePreviewAnswerCollectionEntries"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"answerCollectionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTemplatePreviewAnswerCollectionEntries"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"templateId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"templateId"}}},{"kind":"Argument","name":{"kind":"Name","value":"answerCollectionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"answerCollectionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]} as unknown as DocumentNode<GetTemplatePreviewAnswerCollectionEntriesQuery, GetTemplatePreviewAnswerCollectionEntriesQueryVariables>;
 export const GetUnassignedLiveQuizzesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUnassignedLiveQuizzes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unassignedLiveQuizzes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<GetUnassignedLiveQuizzesQuery, GetUnassignedLiveQuizzesQueryVariables>;
 export const GetUserCoursesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserCourses"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userCourses"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pinCode"}},{"kind":"Field","name":{"kind":"Name","value":"isArchived"}},{"kind":"Field","name":{"kind":"Name","value":"isGamificationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isGroupCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<GetUserCoursesQuery, GetUserCoursesQueryVariables>;
+export const GetUserGroupsUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserGroupsUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getUserGroupsUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"numOfMembers"}},{"kind":"Field","name":{"kind":"Name","value":"isMember"}},{"kind":"Field","name":{"kind":"Name","value":"isAdmin"}},{"kind":"Field","name":{"kind":"Name","value":"isOwner"}}]}}]}}]} as unknown as DocumentNode<GetUserGroupsUserQuery, GetUserGroupsUserQueryVariables>;
 export const GetUserLiveQuizzesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserLiveQuizzes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userLiveQuizzes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"namespace"}},{"kind":"Field","name":{"kind":"Name","value":"templateId"}},{"kind":"Field","name":{"kind":"Name","value":"templateName"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"accessMode"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"numOfBlocks"}},{"kind":"Field","name":{"kind":"Name","value":"numOfInstances"}},{"kind":"Field","name":{"kind":"Name","value":"isGamificationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfusionFeedbackEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isLiveQAEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isModerationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"blocks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"numOfParticipants"}},{"kind":"Field","name":{"kind":"Name","value":"elements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"elementType"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ElementData"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"course"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ElementData"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ElementInstance"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"elementData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ChoicesElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"hasAnswerFeedbacks"}},{"kind":"Field","name":{"kind":"Name","value":"displayMode"}},{"kind":"Field","name":{"kind":"Name","value":"choices"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ix"}},{"kind":"Field","name":{"kind":"Name","value":"correct"}},{"kind":"Field","name":{"kind":"Name","value":"feedback"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NumericalElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"accuracy"}},{"kind":"Field","name":{"kind":"Name","value":"placeholder"}},{"kind":"Field","name":{"kind":"Name","value":"unit"}},{"kind":"Field","name":{"kind":"Name","value":"restrictions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}},{"kind":"Field","name":{"kind":"Name","value":"solutionRanges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exactSolutions"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FreeTextElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"restrictions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"maxLength"}}]}},{"kind":"Field","name":{"kind":"Name","value":"solutions"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SelectionElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfInputs"}},{"kind":"Field","name":{"kind":"Name","value":"answerCollection"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"entries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"answerCollectionSolutionIds"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CaseStudyElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasSampleSolution"}},{"kind":"Field","name":{"kind":"Name","value":"answerCollectionId"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}},{"kind":"Field","name":{"kind":"Name","value":"step"}},{"kind":"Field","name":{"kind":"Name","value":"unit"}},{"kind":"Field","name":{"kind":"Name","value":"labels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"mid"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"cases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"solutions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"itemId"}},{"kind":"Field","name":{"kind":"Name","value":"criteriaSolutions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"criterionId"}},{"kind":"Field","name":{"kind":"Name","value":"min"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FlashcardElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ContentElementData"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"elementId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"explanation"}},{"kind":"Field","name":{"kind":"Name","value":"basePoints"}},{"kind":"Field","name":{"kind":"Name","value":"pointsMultiplier"}}]}}]}}]}}]} as unknown as DocumentNode<GetUserLiveQuizzesQuery, GetUserLiveQuizzesQueryVariables>;
 export const GetUserLoginsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserLogins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userLogins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"lastLoginAt"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"userScope"}}]}}]} as unknown as DocumentNode<GetUserLoginsQuery, GetUserLoginsQueryVariables>;
 export const GetUserMediaFilesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserMediaFiles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMediaFiles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"href"}}]}}]}}]} as unknown as DocumentNode<GetUserMediaFilesQuery, GetUserMediaFilesQueryVariables>;
diff --git a/packages/graphql/src/public/client.json b/packages/graphql/src/public/client.json
index e49c699f01..f32feefcb7 100644
--- a/packages/graphql/src/public/client.json
+++ b/packages/graphql/src/public/client.json
@@ -30,7 +30,7 @@
    "CreateParticipantAccount": "278b145dc084311fdb228613ded6458695bf03f8df62be76fcc6b7a8a1e01b44",
    "CreateParticipantGroup": "cad4e08adef764c01259e1c20738c67d7ba20d6fe9bea3e9cff18ff6024d05a1",
    "CreatePracticeQuiz": "8601004d06205bdee97205b0b38122c4ba58ae19e2e7490ad41b209595390a71",
-   "CreateUserGroup": "7689c0d5b09abec57e44403e950fc5f69bfb77bff6d086e2efb35821f1385abe",
+   "CreateUserGroup": "cd4146319a0b2fa544a7cec156ba89f4ba7bdc1fec60ce01904cb190cd98276e",
    "CreateUserLogin": "e73c6e12667bbaccdb5ae060522bab74096df7f59c2ebb5c80a510d9c8c4a3de",
    "DeactivateLiveQuizBlock": "334f93a38d1f8a42910c37652f4a222b6d0f5e6bde12c0647a267ddc16e2b1a3",
    "DeclineObjectSharingRequest": "fde193f7b5bf00b20a47c12d4c5d5a8b769075083943e4bd153195171c2f82e6",
@@ -48,6 +48,7 @@
    "DeletePracticeQuiz": "82ffabb50f040318c4bfa36c356516a4d68865fe0ccb4d2ba1bc2dd8df68d800",
    "DeleteQuestion": "90cc0ecb00c5fe308042869b95fb0a239f483a7bf5c566d508e410d2ea0ac869",
    "DeleteTag": "a682e0285401766d6fea37da6742ccd8bf288de7996c6898db22344959cd14a6",
+   "DeleteUserGroup": "299ab1a532934b2deeb4d981fd173d516ec4430f8de399382c6ac97489623d0d",
    "DeleteUserLogin": "0439c352372a7b374ab58927cc48b8af8af84c844883b6edd37200c744405ad8",
    "EditActivityTemplate": "2f072fcfa325b8708317f2569765f0d105f2ef7ad49dacb391536d968e14d694",
    "EditAnswerCollectionEntry": "4f390a652c7dd8d364f515f434c9b58041d42b044eb933f794491f8a67096783",
@@ -78,6 +79,7 @@
    "LeaveCourseLeaderboard": "99cf3b62968ab73f696d53ad20134c31a4e571fb256f1f66b872320bdadf216b",
    "LeaveParticipantGroup": "9754dc9536620f85179133d414ebafa1019b5ca0936dee994574e16f2a3332c9",
    "LeaveRandomCourseGroupPool": "18e99eb43c4b391273ae3fc378d7212cc45c6013fd6791dc2b25283eb276c154",
+   "LeaveUserGroup": "3eb07d3cacf7eaf0bd23541d00f8391aa7eaa88a5ea96dfa5ab0119ae81fc70b",
    "LoginParticipant": "8e3ea39b64b0408ac5e307fc0778e8ad583268eec5569d98873ddec90598c384",
    "LoginParticipantMagicLink": "69f89c2a6f2af57ee528cd40977d6ab1e52002d6641ac03f8490d1267b3cacd9",
    "LoginParticipantWithLti": "e1622642b3708501940653a758401962a2590789a3f60babbb2d31b85a64b6de",
@@ -222,6 +224,7 @@
    "GetTemplatePreviewAnswerCollectionEntries": "469ba4ec08f5efb4a41c9f8ee8604f413bd7337b21a6cfdad934946fea0834b8",
    "GetUnassignedLiveQuizzes": "8efbb0afbb0826d5e237cbaa660f62c201d339b74d1907336bb8a0a5db519f73",
    "GetUserCourses": "b388f07ace660915ce9ea4006847f9367251d494e7d0e387fa61ef50684fd9a4",
+   "GetUserGroupsUser": "c8bbc435e4cd3450f2bbe60293f2bf2ddd328e424dcc631dae44435f285bc84c",
    "GetUserLiveQuizzes": "8fa939bfc975899f435995a9dfb0e9350adcd7b4aec0ec2b4be9f890b581e035",
    "GetUserLogins": "7b289235e4ea8e3fb2073ae175b1b900a77cb6c1aae9b12580cc08be825ad1aa",
    "GetUserMediaFiles": "af5513e8c56c1805060f0e2854a4ada90c4484d09481f2ba2a59136cb731cd63",
diff --git a/packages/graphql/src/public/schema.graphql b/packages/graphql/src/public/schema.graphql
index 9f83852ac9..08e2c1ea8e 100644
--- a/packages/graphql/src/public/schema.graphql
+++ b/packages/graphql/src/public/schema.graphql
@@ -1401,6 +1401,7 @@ type Mutation {
   deletePracticeQuiz(id: String!): PracticeQuiz
   deleteQuestion(id: Int!): Element
   deleteTag(id: Int!): Tag
+  deleteUserGroup(groupId: Int!): Boolean!
   deleteUserLogin(id: String!): UserLogin
   editActivityTemplate(activityId: String!, activityType: ActivityType!, description: String!, instructions: String!, name: String!, templateId: String!): Boolean!
   editAnswerCollectionEntry(collectionId: Int!, id: Int!, value: String!): AnswerCollectionEntry
@@ -1431,6 +1432,7 @@ type Mutation {
   leaveCourseLeaderboard(courseId: String!): LeaveCourseParticipation
   leaveParticipantGroup(courseId: String!, groupId: String!): ParticipantGroup
   leaveRandomCourseGroupPool(courseId: String!): Boolean!
+  leaveUserGroup(groupId: Int!): Boolean!
   loginParticipant(password: String!, usernameOrEmail: String!): ID
   loginParticipantMagicLink(token: String!): ID
   loginParticipantWithLti(courseId: String, signedLtiData: String!): ParticipantTokenData
@@ -1906,6 +1908,7 @@ type Query {
   getStudentCourseLeaderboard(courseId: String!, mode: String!): StudentCourseLeaderboard
   getTemplateInformation(activityId: String!, activityType: ActivityType!): ActivityTemplateMetadata
   getTemplatePreviewAnswerCollectionEntries(answerCollectionId: Int!, templateId: String!): [AnswerCollectionPreviewEntry!]
+  getUserGroupsUser: [UserGroup!]
   getUsersPrivatePreview: [UserInfo!]
   groupActivities(courseId: String!): [GroupActivity!]
   groupActivity(id: String!): GroupActivity
@@ -2276,6 +2279,9 @@ type User {
 type UserGroup {
   admins: [UserInfo!]
   id: Int!
+  isAdmin: Boolean
+  isMember: Boolean
+  isOwner: Boolean
   members: [UserInfo!]
   name: String!
   numOfMembers: Int
diff --git a/packages/graphql/src/public/server.json b/packages/graphql/src/public/server.json
index d66eba6cd7..5910b3a482 100644
--- a/packages/graphql/src/public/server.json
+++ b/packages/graphql/src/public/server.json
@@ -30,7 +30,7 @@
    "278b145dc084311fdb228613ded6458695bf03f8df62be76fcc6b7a8a1e01b44": "mutation CreateParticipantAccount($email: String!, $username: String!, $password: String!, $isProfilePublic: Boolean!, $courseId: String, $signedLtiData: String) {\n  createParticipantAccount(\n    email: $email\n    username: $username\n    password: $password\n    isProfilePublic: $isProfilePublic\n    courseId: $courseId\n    signedLtiData: $signedLtiData\n  ) {\n    participantToken\n    participant {\n      id\n      email\n      username\n      __typename\n    }\n    __typename\n  }\n}",
    "cad4e08adef764c01259e1c20738c67d7ba20d6fe9bea3e9cff18ff6024d05a1": "mutation CreateParticipantGroup($courseId: String!, $name: String!) {\n  createParticipantGroup(courseId: $courseId, name: $name) {\n    id\n    name\n    code\n    participants {\n      id\n      username\n      __typename\n    }\n    __typename\n  }\n}",
    "8601004d06205bdee97205b0b38122c4ba58ae19e2e7490ad41b209595390a71": "fragment ElementData on ElementInstance {\n  elementData {\n    ... on ChoicesElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        hasAnswerFeedbacks\n        displayMode\n        choices {\n          ix\n          correct\n          feedback\n          value\n          __typename\n        }\n        __typename\n      }\n    }\n    ... on NumericalElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        accuracy\n        placeholder\n        unit\n        restrictions {\n          min\n          max\n          __typename\n        }\n        solutionRanges {\n          min\n          max\n          __typename\n        }\n        exactSolutions\n        __typename\n      }\n    }\n    ... on FreeTextElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        restrictions {\n          maxLength\n          __typename\n        }\n        solutions\n        __typename\n      }\n    }\n    ... on SelectionElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        numberOfInputs\n        answerCollection {\n          id\n          entries {\n            id\n            value\n            __typename\n          }\n          __typename\n        }\n        answerCollectionSolutionIds\n        __typename\n      }\n    }\n    ... on CaseStudyElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        answerCollectionId\n        items {\n          id\n          value\n          __typename\n        }\n        criteria {\n          id\n          name\n          min\n          max\n          step\n          unit\n          labels {\n            min\n            mid\n            max\n            __typename\n          }\n          __typename\n        }\n        cases {\n          id\n          title\n          description\n          solutions {\n            itemId\n            criteriaSolutions {\n              criterionId\n              min\n              max\n              __typename\n            }\n            __typename\n          }\n          __typename\n        }\n        __typename\n      }\n    }\n    ... on FlashcardElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n    }\n    ... on ContentElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n    }\n    __typename\n  }\n  __typename\n}\nmutation CreatePracticeQuiz($name: String!, $displayName: String!, $description: String, $stacks: [ElementStackInput!]!, $courseId: String!, $multiplier: Int!, $order: ElementOrderType!, $resetTimeDays: Int!) {\n  createPracticeQuiz(\n    name: $name\n    displayName: $displayName\n    description: $description\n    stacks: $stacks\n    courseId: $courseId\n    multiplier: $multiplier\n    order: $order\n    resetTimeDays: $resetTimeDays\n  ) {\n    id\n    name\n    displayName\n    description\n    pointsMultiplier\n    resetTimeDays\n    orderType\n    course {\n      id\n      __typename\n    }\n    stacks {\n      id\n      displayName\n      description\n      elements {\n        id\n        ...ElementData\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n}",
-   "7689c0d5b09abec57e44403e950fc5f69bfb77bff6d086e2efb35821f1385abe": "mutation CreateUserGroup($name: String!, $members: [UserGroupMembersInput!]!) {\n  createUserGroup(name: $name, members: $members) {\n    __typename\n    id\n    name\n    members {\n      id\n      shortname\n      email\n      __typename\n    }\n    admins {\n      id\n      shortname\n      email\n      __typename\n    }\n  }\n}",
+   "cd4146319a0b2fa544a7cec156ba89f4ba7bdc1fec60ce01904cb190cd98276e": "mutation CreateUserGroup($name: String!, $members: [UserGroupMembersInput!]!) {\n  createUserGroup(name: $name, members: $members) {\n    id\n    name\n    members {\n      id\n      shortname\n      email\n      __typename\n    }\n    admins {\n      id\n      shortname\n      email\n      __typename\n    }\n    numOfMembers\n    isMember\n    isAdmin\n    isOwner\n    __typename\n  }\n}",
    "e73c6e12667bbaccdb5ae060522bab74096df7f59c2ebb5c80a510d9c8c4a3de": "mutation CreateUserLogin($password: String!, $name: String!, $scope: UserLoginScope!) {\n  createUserLogin(password: $password, name: $name, scope: $scope) {\n    id\n    name\n    scope\n    user {\n      id\n      shortname\n      __typename\n    }\n    __typename\n  }\n}",
    "334f93a38d1f8a42910c37652f4a222b6d0f5e6bde12c0647a267ddc16e2b1a3": "mutation DeactivateLiveQuizBlock($quizId: String!, $blockId: Int!) {\n  deactivateLiveQuizBlock(quizId: $quizId, blockId: $blockId) {\n    id\n    status\n    activeBlock {\n      id\n      __typename\n    }\n    blocks {\n      id\n      status\n      __typename\n    }\n    __typename\n  }\n}",
    "fde193f7b5bf00b20a47c12d4c5d5a8b769075083943e4bd153195171c2f82e6": "mutation DeclineObjectSharingRequest($requestId: Int!, $userId: String!) {\n  declineObjectSharingRequest(requestId: $requestId, userId: $userId)\n}",
@@ -48,6 +48,7 @@
    "82ffabb50f040318c4bfa36c356516a4d68865fe0ccb4d2ba1bc2dd8df68d800": "mutation DeletePracticeQuiz($id: String!) {\n  deletePracticeQuiz(id: $id) {\n    id\n    __typename\n  }\n}",
    "90cc0ecb00c5fe308042869b95fb0a239f483a7bf5c566d508e410d2ea0ac869": "mutation DeleteQuestion($id: Int!) {\n  deleteQuestion(id: $id) {\n    ... on ChoicesElement {\n      id\n      __typename\n    }\n    ... on NumericalElement {\n      id\n      __typename\n    }\n    ... on FreeTextElement {\n      id\n      __typename\n    }\n    ... on SelectionElement {\n      id\n      __typename\n    }\n    ... on CaseStudyElement {\n      id\n      __typename\n    }\n    ... on FlashcardElement {\n      id\n      __typename\n    }\n    ... on ContentElement {\n      id\n      __typename\n    }\n    __typename\n  }\n}",
    "a682e0285401766d6fea37da6742ccd8bf288de7996c6898db22344959cd14a6": "mutation DeleteTag($id: Int!) {\n  deleteTag(id: $id) {\n    id\n    __typename\n  }\n}",
+   "299ab1a532934b2deeb4d981fd173d516ec4430f8de399382c6ac97489623d0d": "mutation DeleteUserGroup($groupId: Int!) {\n  deleteUserGroup(groupId: $groupId)\n}",
    "0439c352372a7b374ab58927cc48b8af8af84c844883b6edd37200c744405ad8": "mutation DeleteUserLogin($id: String!) {\n  deleteUserLogin(id: $id) {\n    id\n    __typename\n  }\n}",
    "2f072fcfa325b8708317f2569765f0d105f2ef7ad49dacb391536d968e14d694": "mutation EditActivityTemplate($activityId: String!, $activityType: ActivityType!, $templateId: String!, $name: String!, $description: String!, $instructions: String!) {\n  editActivityTemplate(\n    activityId: $activityId\n    activityType: $activityType\n    templateId: $templateId\n    name: $name\n    description: $description\n    instructions: $instructions\n  )\n}",
    "4f390a652c7dd8d364f515f434c9b58041d42b044eb933f794491f8a67096783": "mutation EditAnswerCollectionEntry($id: Int!, $value: String!, $collectionId: Int!) {\n  editAnswerCollectionEntry(id: $id, value: $value, collectionId: $collectionId) {\n    id\n    value\n    __typename\n  }\n}",
@@ -78,6 +79,7 @@
    "99cf3b62968ab73f696d53ad20134c31a4e571fb256f1f66b872320bdadf216b": "mutation LeaveCourseLeaderboard($courseId: String!) {\n  leaveCourseLeaderboard(courseId: $courseId) {\n    id\n    participation {\n      id\n      isActive\n      __typename\n    }\n    __typename\n  }\n}",
    "9754dc9536620f85179133d414ebafa1019b5ca0936dee994574e16f2a3332c9": "mutation LeaveParticipantGroup($groupId: String!, $courseId: String!) {\n  leaveParticipantGroup(groupId: $groupId, courseId: $courseId) {\n    id\n    name\n    code\n    participants {\n      id\n      username\n      __typename\n    }\n    __typename\n  }\n}",
    "18e99eb43c4b391273ae3fc378d7212cc45c6013fd6791dc2b25283eb276c154": "mutation LeaveRandomCourseGroupPool($courseId: String!) {\n  leaveRandomCourseGroupPool(courseId: $courseId)\n}",
+   "3eb07d3cacf7eaf0bd23541d00f8391aa7eaa88a5ea96dfa5ab0119ae81fc70b": "mutation LeaveUserGroup($groupId: Int!) {\n  leaveUserGroup(groupId: $groupId)\n}",
    "8e3ea39b64b0408ac5e307fc0778e8ad583268eec5569d98873ddec90598c384": "mutation LoginParticipant($usernameOrEmail: String!, $password: String!) {\n  loginParticipant(usernameOrEmail: $usernameOrEmail, password: $password)\n}",
    "69f89c2a6f2af57ee528cd40977d6ab1e52002d6641ac03f8490d1267b3cacd9": "mutation LoginParticipantMagicLink($token: String!) {\n  loginParticipantMagicLink(token: $token)\n}",
    "e1622642b3708501940653a758401962a2590789a3f60babbb2d31b85a64b6de": "mutation LoginParticipantWithLti($signedLtiData: String!, $courseId: String) {\n  loginParticipantWithLti(signedLtiData: $signedLtiData, courseId: $courseId) {\n    participant {\n      id\n      __typename\n    }\n    participantToken\n    __typename\n  }\n}",
@@ -222,6 +224,7 @@
    "469ba4ec08f5efb4a41c9f8ee8604f413bd7337b21a6cfdad934946fea0834b8": "query GetTemplatePreviewAnswerCollectionEntries($templateId: String!, $answerCollectionId: Int!) {\n  getTemplatePreviewAnswerCollectionEntries(\n    templateId: $templateId\n    answerCollectionId: $answerCollectionId\n  ) {\n    id\n    value\n    __typename\n  }\n}",
    "8efbb0afbb0826d5e237cbaa660f62c201d339b74d1907336bb8a0a5db519f73": "query GetUnassignedLiveQuizzes {\n  unassignedLiveQuizzes {\n    id\n    name\n    status\n    __typename\n  }\n}",
    "b388f07ace660915ce9ea4006847f9367251d494e7d0e387fa61ef50684fd9a4": "query GetUserCourses {\n  userCourses {\n    id\n    name\n    displayName\n    color\n    pinCode\n    isArchived\n    isGamificationEnabled\n    isGroupCreationEnabled\n    description\n    startDate\n    endDate\n    createdAt\n    updatedAt\n    __typename\n  }\n}",
+   "c8bbc435e4cd3450f2bbe60293f2bf2ddd328e424dcc631dae44435f285bc84c": "query GetUserGroupsUser {\n  getUserGroupsUser {\n    id\n    name\n    members {\n      id\n      shortname\n      email\n      __typename\n    }\n    admins {\n      id\n      shortname\n      email\n      __typename\n    }\n    numOfMembers\n    isMember\n    isAdmin\n    isOwner\n    __typename\n  }\n}",
    "8fa939bfc975899f435995a9dfb0e9350adcd7b4aec0ec2b4be9f890b581e035": "fragment ElementData on ElementInstance {\n  elementData {\n    ... on ChoicesElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        hasAnswerFeedbacks\n        displayMode\n        choices {\n          ix\n          correct\n          feedback\n          value\n          __typename\n        }\n        __typename\n      }\n    }\n    ... on NumericalElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        accuracy\n        placeholder\n        unit\n        restrictions {\n          min\n          max\n          __typename\n        }\n        solutionRanges {\n          min\n          max\n          __typename\n        }\n        exactSolutions\n        __typename\n      }\n    }\n    ... on FreeTextElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        restrictions {\n          maxLength\n          __typename\n        }\n        solutions\n        __typename\n      }\n    }\n    ... on SelectionElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        numberOfInputs\n        answerCollection {\n          id\n          entries {\n            id\n            value\n            __typename\n          }\n          __typename\n        }\n        answerCollectionSolutionIds\n        __typename\n      }\n    }\n    ... on CaseStudyElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n      options {\n        hasSampleSolution\n        answerCollectionId\n        items {\n          id\n          value\n          __typename\n        }\n        criteria {\n          id\n          name\n          min\n          max\n          step\n          unit\n          labels {\n            min\n            mid\n            max\n            __typename\n          }\n          __typename\n        }\n        cases {\n          id\n          title\n          description\n          solutions {\n            itemId\n            criteriaSolutions {\n              criterionId\n              min\n              max\n              __typename\n            }\n            __typename\n          }\n          __typename\n        }\n        __typename\n      }\n    }\n    ... on FlashcardElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n    }\n    ... on ContentElementData {\n      __typename\n      id\n      elementId\n      name\n      type\n      content\n      explanation\n      basePoints\n      pointsMultiplier\n    }\n    __typename\n  }\n  __typename\n}\nquery GetUserLiveQuizzes {\n  userLiveQuizzes {\n    id\n    name\n    displayName\n    namespace\n    templateId\n    templateName\n    pointsMultiplier\n    accessMode\n    status\n    createdAt\n    updatedAt\n    startedAt\n    finishedAt\n    numOfBlocks\n    numOfInstances\n    isGamificationEnabled\n    isConfusionFeedbackEnabled\n    isLiveQAEnabled\n    isModerationEnabled\n    blocks {\n      id\n      order\n      status\n      numOfParticipants\n      elements {\n        id\n        type\n        elementType\n        ...ElementData\n        __typename\n      }\n      __typename\n    }\n    course {\n      id\n      name\n      displayName\n      __typename\n    }\n    __typename\n  }\n}",
    "7b289235e4ea8e3fb2073ae175b1b900a77cb6c1aae9b12580cc08be825ad1aa": "query GetUserLogins {\n  userLogins {\n    id\n    name\n    scope\n    lastLoginAt\n    user {\n      id\n      shortname\n      __typename\n    }\n    __typename\n  }\n  userScope\n}",
    "af5513e8c56c1805060f0e2854a4ada90c4484d09481f2ba2a59136cb731cd63": "query GetUserMediaFiles {\n  userMediaFiles {\n    id\n    name\n    type\n    href\n    __typename\n  }\n}",
diff --git a/packages/graphql/src/schema/mutation.ts b/packages/graphql/src/schema/mutation.ts
index 256358ce75..14a74bf84b 100644
--- a/packages/graphql/src/schema/mutation.ts
+++ b/packages/graphql/src/schema/mutation.ts
@@ -1564,6 +1564,24 @@ export const Mutation = builder.mutationType({
         },
       }),
 
+      leaveUserGroup: t.withAuth(asUserFullAccess).boolean({
+        args: {
+          groupId: t.arg.int({ required: true }),
+        },
+        resolve: async (_, args, ctx) => {
+          return await SharingService.leaveUserGroup(args, ctx)
+        },
+      }),
+
+      deleteUserGroup: t.withAuth(asUserFullAccess).boolean({
+        args: {
+          groupId: t.arg.int({ required: true }),
+        },
+        resolve: async (_, args, ctx) => {
+          return await SharingService.deleteUserGroup(args, ctx)
+        },
+      }),
+
       addObjectToCatalog: t.withAuth(asUserFullAccess).field({
         nullable: true,
         type: CatalogObject,
diff --git a/packages/graphql/src/schema/query.ts b/packages/graphql/src/schema/query.ts
index 1b2f577a04..50692af8d3 100644
--- a/packages/graphql/src/schema/query.ts
+++ b/packages/graphql/src/schema/query.ts
@@ -79,6 +79,7 @@ import {
   DerivedPermissionInfo,
   ObjectSharingRequest,
   PermissionInfo,
+  UserGroup,
 } from './sharing.js'
 import {
   ActivityTemplate,
@@ -1320,6 +1321,14 @@ export const Query = builder.queryType({
         },
       }),
 
+      getUserGroupsUser: t.withAuth(asUser).field({
+        nullable: true,
+        type: [UserGroup],
+        resolve: async (_, __, ctx) => {
+          return await SharingService.getUserGroupsUser(ctx)
+        },
+      }),
+
       getObjectPermissions: t.withAuth(asUser).field({
         nullable: true,
         type: [PermissionInfo],
diff --git a/packages/graphql/src/schema/sharing.ts b/packages/graphql/src/schema/sharing.ts
index a50149cadf..b989b088c3 100644
--- a/packages/graphql/src/schema/sharing.ts
+++ b/packages/graphql/src/schema/sharing.ts
@@ -167,6 +167,9 @@ interface IUserGroup extends DB.UserGroup {
   numOfMembers?: number
   members?: IUserInfo[]
   admins?: IUserInfo[]
+  isMember?: boolean
+  isAdmin?: boolean
+  isOwner?: boolean
 }
 export const UserGroupRef = builder.objectRef<IUserGroup>('UserGroup')
 export const UserGroup = UserGroupRef.implement({
@@ -182,6 +185,9 @@ export const UserGroup = UserGroupRef.implement({
       nullable: true,
     }),
     numOfMembers: t.exposeInt('numOfMembers', { nullable: true }),
+    isMember: t.exposeBoolean('isMember', { nullable: true }),
+    isAdmin: t.exposeBoolean('isAdmin', { nullable: true }),
+    isOwner: t.exposeBoolean('isOwner', { nullable: true }),
   }),
 })
 
diff --git a/packages/graphql/src/services/sharing.ts b/packages/graphql/src/services/sharing.ts
index 09108231ad..b69a8aca26 100644
--- a/packages/graphql/src/services/sharing.ts
+++ b/packages/graphql/src/services/sharing.ts
@@ -4342,6 +4342,246 @@ export async function createUserGroup(
   return {
     ...newUserGroup,
     numOfMembers: newUserGroup.members.length + newUserGroup.admins.length + 1,
+    isMember: false,
+    isAdmin: false,
+    isOwner: true,
   }
 }
+
+export async function getUserGroupsUser(ctx: ContextWithUser) {
+  const user = await ctx.prisma.user.findUnique({
+    where: { id: ctx.user.sub },
+    include: {
+      // user is MEMBER
+      userGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is ADMIN
+      adminUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is OWNER
+      managedUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+    },
+  })
+
+  if (!user) {
+    return null
+  }
+
+  return [
+    ...user.userGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: true,
+      isAdmin: false,
+      isOwner: false,
+    })),
+    ...user.adminUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: true,
+      isOwner: false,
+    })),
+    ...user.managedUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: false,
+      isOwner: true,
+    })),
+  ]
+}
+
+// TODO: add unit testing for this function
+export async function leaveUserGroup(
+  { groupId }: { groupId: number },
+  ctx: ContextWithUser
+) {
+  // check if the user is a member or admin of the group
+  const userGroup = await ctx.prisma.userGroup.findUnique({
+    where: { id: groupId },
+    include: {
+      members: true,
+      admins: true,
+    },
+  })
+
+  if (
+    !userGroup ||
+    (userGroup.members.length === 0 && userGroup.admins.length === 0)
+  ) {
+    return false
+  }
+
+  // remove the user from the group
+  const updatedUserGroup = await ctx.prisma.userGroup.update({
+    where: { id: groupId },
+    data: {
+      members:
+        userGroup.members.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+      admins:
+        userGroup.admins.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+    },
+    include: {
+      permissions: true,
+    },
+  })
+
+  // trigger a derived permission recomputation for all objects that were shared with this group and this user
+  for (const permission of updatedUserGroup.permissions) {
+    if (permission.catalogCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          catalogCollectionId: permission.catalogCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.answerCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          answerCollectionId: permission.answerCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.elementId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          elementId: permission.elementId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.courseId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          courseId: permission.courseId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.liveQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          liveQuizId: permission.liveQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.practiceQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          practiceQuizId: permission.practiceQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.microLearningId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          microLearningId: permission.microLearningId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.groupActivityId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          groupActivityId: permission.groupActivityId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    }
+  }
+
+  return true
+}
+
+// TODO: add unit testing for this function
+export async function deleteUserGroup(
+  { groupId }: { groupId: number },
+  ctx: ContextWithUser
+) {
+  // check if the user is the owner of the group
+  const userGroup = await ctx.prisma.userGroup.findUnique({
+    where: { id: groupId, ownerId: ctx.user.sub },
+    include: { permissions: true },
+  })
+
+  if (!userGroup) {
+    return false
+  }
+
+  // delete the user group
+  await ctx.prisma.userGroup.delete({
+    where: { id: groupId },
+  })
+
+  // recompute the permissions for all objects that were shared with this user gruop
+  for (const permission of userGroup.permissions) {
+    if (permission.catalogCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        { catalogCollectionId: permission.catalogCollectionId },
+        ctx.prisma
+      )
+    } else if (permission.answerCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        { answerCollectionId: permission.answerCollectionId },
+        ctx.prisma
+      )
+    } else if (permission.elementId !== null) {
+      await recomputeDerivedPermissions(
+        { elementId: permission.elementId },
+        ctx.prisma
+      )
+    } else if (permission.courseId !== null) {
+      await recomputeDerivedPermissions(
+        { courseId: permission.courseId },
+        ctx.prisma
+      )
+    } else if (permission.liveQuizId !== null) {
+      await recomputeDerivedPermissions(
+        { liveQuizId: permission.liveQuizId },
+        ctx.prisma
+      )
+    } else if (permission.practiceQuizId !== null) {
+      await recomputeDerivedPermissions(
+        { practiceQuizId: permission.practiceQuizId },
+        ctx.prisma
+      )
+    } else if (permission.microLearningId !== null) {
+      await recomputeDerivedPermissions(
+        { microLearningId: permission.microLearningId },
+        ctx.prisma
+      )
+    } else if (permission.groupActivityId !== null) {
+      await recomputeDerivedPermissions(
+        { groupActivityId: permission.groupActivityId },
+        ctx.prisma
+      )
+    }
+  }
+
+  return true
+}
+
 // #endregion
diff --git a/packages/i18n/messages/de.ts b/packages/i18n/messages/de.ts
index 2bb4d00177..976d9b6deb 100644
--- a/packages/i18n/messages/de.ts
+++ b/packages/i18n/messages/de.ts
@@ -2405,6 +2405,7 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
         'Bitte geben Sie einen Namen für die Nutzergruppe an. Dieser Name wird benötigt, um die Gruppe zu identifizieren.',
       nameRequired: 'Die Eingabe eines Namens für die Gruppe ist erforderlich.',
       member: 'Mitglied',
+      members: 'Mitglieder',
       admin: 'Admin',
       addMember: 'Mitglied hinzufügen',
       emailOrShortname: 'Email oder Kurzname',
@@ -2417,6 +2418,16 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
       creationSuccessMessage: 'Die Nutzergruppe wurde erfolgreich erstellt.',
       creationErrorMessage:
         'Bei der Erstellung der Nutzergruppe ist ein Fehler aufgetreten. Bitte stellen Sie sicher, dass gültige Angeben für mindestens einen Nutzer (nicht Sie selbst) eingegeben wurden und dass keine andere Nutzergruppe mit demselben Namen bereits existiert.',
+      viewGroup: 'Nutzergruppe ansehen',
+      editGroup: 'Nutzergruppe bearbeiten',
+      leaveGroup: 'Nutzergruppe verlassen',
+      deleteGroup: 'Nutzergruppe löschen',
+      confirmLeaveGroup:
+        'Sie sind dabei, die Nutzergruppe {groupName} zu verlassen. Durch das Verlassen dieser Gruppe verlieren Sie den Zugriff auf alle Objekte, die mit dieser Gruppe geteilt wurden. Wenn Sie sicher sind, bestätigen Sie bitte diese Aktion.',
+      leaveGroupSuccess:
+        'Sie haben die Nutzergruppe wurde erfolgreich verlassen.',
+      leaveGroupError:
+        'Beim Verlassen der Nutzergruppe ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support.',
     },
     catalog: {
       accessTypes: 'Zugriffs-Typen',
diff --git a/packages/i18n/messages/en.ts b/packages/i18n/messages/en.ts
index c76aa4e200..cfef1da883 100644
--- a/packages/i18n/messages/en.ts
+++ b/packages/i18n/messages/en.ts
@@ -2376,6 +2376,7 @@ Since the KlickerUZH app is not yet available on the iOS App Store, follow these
         'Please enter a name for the user group. This name will be used to identify the group.',
       nameRequired: 'Please enter a name for the group.',
       member: 'Member',
+      members: 'Members',
       admin: 'Admin',
       addMember: 'Add Member',
       emailOrShortname: 'Email or shortname',
@@ -2387,6 +2388,26 @@ Since the KlickerUZH app is not yet available on the iOS App Store, follow these
       creationSuccessMessage: 'The user group was created successfully.',
       creationErrorMessage:
         'An error occurred while creating the user group. Please ensure that at information for at least one valid member (not yourself) was provided and that no other user group with the same name already exists.',
+      viewGroup: 'View User Group',
+      editGroup: 'Edit User Group',
+      leaveGroup: 'Leave User Group',
+      deleteGroup: 'Delete User Group',
+      confirmLeaveGroup:
+        'You are about to leave the user group {groupName}. By leaving this group, you will loose access to all objects that are shared with this group. If you are sure, please confirm this action.',
+      leaveGroupSuccess: 'You have successfully left the user group.',
+      leaveGroupError:
+        'An error occurred while leaving the user group. Please try again or contact the support.',
+      deleteGroupSuccess: 'You have successfully deleted the user group.',
+      deleteGroupError:
+        'An error occurred while deleting the user group. Please try again or contact the support.',
+      confirmDeleteGroup:
+        'Please review the effects of deleting this user group "{groupName}" carefully and confirm them before finalizing the deletion.',
+      resolveGroupConfirmation:
+        'By resolving this group, all members and admins will be removed from it.',
+      revokeDirectPermissionsConfirmation:
+        'All direct permissions granted to the group will be revoked, all group members will loose access to the corresponding objects.',
+      irreversibleActionConfirmation:
+        'This action is irreversible and cannot be undone.',
     },
     catalog: {
       accessTypes: 'Access Types',


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx (1)</summary><blockquote>

`23-29`: **Consider adding empty state handling.**

The component correctly handles the loading state but doesn't have specific UI for when no user groups exist.

You might want to add a message when `data?.getUserGroupsUser?.length === 0`:

```diff
{loading ? (
  <Loader />
) : (
+  data?.getUserGroupsUser?.length ? (
    data?.getUserGroupsUser?.map((group) => (
      <UserGroupEntry key={`group-item-${group.id}`} group={group} />
    ))
+  ) : (
+    <div className="text-gray-500">{t('manage.userGroups.noGroupsYet')}</div>
+  )
)}
```

</blockquote></details>
<details>
<summary>packages/i18n/messages/en.ts (2)</summary><blockquote>

`2398-2399`: **Refine error message phrasing**  
Consider dropping “the” before “support” for smoother English.  
  
```diff
- 'An error occurred while leaving the user group. Please try again or contact the support.',
+ 'An error occurred while leaving the user group. Please try again or contact support.',
```

---

`2401-2402`: **Refine error message phrasing**  
Similarly, remove “the” before “support” in the delete error message.  
  
```diff
- 'An error occurred while deleting the user group. Please try again or contact the support.',
+ 'An error occurred while deleting the user group. Please try again or contact support.',
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupBadge.tsx (1)</summary><blockquote>

`6-44`: **Add role precedence documentation to improve clarity.**

The component correctly handles role display with a clear precedence (owner > admin > member), but this behavior is not explicitly documented.

Consider adding a comment to clarify the role precedence logic:

```diff
function UserGroupBadge({
  isMember,
  isAdmin,
  isOwner,
}: {
  isMember: boolean
  isAdmin: boolean
  isOwner: boolean
}) {
  const t = useTranslations()

+  // Roles are displayed in order of precedence: Owner > Admin > Member
  if (isOwner) {
    return (
      <Badge className="flex flex-row items-center gap-1.5 border-2 border-green-600 bg-green-50 text-black hover:bg-green-100">
        <FontAwesomeIcon icon={faUserTie} />
        <div>{t('shared.generic.owner')}</div>
      </Badge>
    )
  }
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupEntry.tsx (3)</summary><blockquote>

`28-37`: **Update TODOs to reflect implemented functionality.**

Some of the listed TODOs are already implemented in the component (leave group and delete group), which makes the TODO list misleading.

Update the TODOs to reflect the current state of implementation:

```diff
  // required functionalities (ideally incl. audit log entries):
  // TODO: - promote member to ADMIN
  // TODO: - demote ADMIN to MEMBER
  // TODO: - remove member from group
  // TODO: - add member to the group
  // TODO: - edit group name
  // TODO: - transfer ownership
- // TODO: - leave group - only possible for non-owner - ask to confirm in removal modal
- // TODO: - delete group - only possible for owner - ask to confirm in deletion modal
```

---

`71-72`: **Correct the incorrect data-cy attribute.**

The view/edit group option is using a data-cy attribute that implies it's for leaving the group, which could cause confusion in testing.

Fix the data-cy attribute to match the action:

```diff
- data: { cy: `leave-group-${group.name}` },
+ data: { cy: `view-edit-group-${group.name}` },
```

---

`23-23`: **Handle the unused edit modal state.**

The `editModal` state is set when clicking the view/edit button, but there's no corresponding modal implementation yet.

Consider either:
1. Removing the state and click handler until the edit functionality is implemented, or
2. Adding a simple placeholder modal or toast to inform users that this feature is coming soon.

For option 2, you could implement:

```tsx
{editModal && (
  <Modal
    open={editModal}
    onClose={() => setEditModal(false)}
    title={t('manage.userGroups.comingSoon')}
  >
    <div>{t('manage.userGroups.featureUnderDevelopment')}</div>
    <Button onClick={() => setEditModal(false)}>
      <Button.Label>{t('shared.generic.close')}</Button.Label>
    </Button>
  </Modal>
)}
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx (1)</summary><blockquote>

`61-62`: **Rename misleading variable for clarity.**

The variable `catalogObjects` is used to store the result of reading the user groups query, which is misleading.

Rename the variable to better reflect its content:

```diff
- const catalogObjects = cache.readQuery({
+ const userGroupsData = cache.readQuery({
    query: GetUserGroupsUserDocument,
  })
```

Also update the subsequent references to this variable:

```diff
- if (catalogObjects?.getUserGroupsUser) {
+ if (userGroupsData?.getUserGroupsUser) {
    cache.writeQuery({
      query: GetUserGroupsUserDocument,

      data: {
        getUserGroupsUser:
-         catalogObjects?.getUserGroupsUser.filter(
+         userGroupsData?.getUserGroupsUser.filter(
            (group) => group.id !== groupId
          ),
      },
    })
  }
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx (1)</summary><blockquote>

`110-118`: **Shadowed `success` variable inside `update` callback degrades readability**

`const { data: success }` is declared at line 111, but another `const success` is redeclared in the `update` callback (line 116 ⇢ 118).  
While block‑scoped, the reuse of the same identifier is confusing and easy to mis‑read as a bug.

```diff
-              const { data: success } = await deleteUserGroup({
+              const { data: mutationResult } = await deleteUserGroup({
…
-                  const success = data?.deleteUserGroup
-                  if (!success) return
+                  const deletionSucceeded = data?.deleteUserGroup
+                  if (!deletionSucceeded) return
```

</blockquote></details>
<details>
<summary>packages/graphql/src/services/sharing.ts (2)</summary><blockquote>

`4351-4406`: **Duplicate group objects possible – use `Map` to de‑duplicate**

`getUserGroupsUser` concatenates three arrays. If a user is both owner *and* admin/member (possible if the owner was manually added as admin), the same `userGroup` appears multiple times with different flags.

Consider de‑duplicating by `group.id`:

```diff
-  return [
-    ...user.userGroups.map(…),
-    ...user.adminUserGroups.map(…),
-    ...user.managedUserGroups.map(…)
-  ]
+  const merged = new Map<number, ReturnType<typeof mapFn>>()  // mapFn = your mapper
+  ;[ ...user.userGroups, ...user.adminUserGroups, ...user.managedUserGroups ].forEach(g => {
+    const roleFlags = /* derive flags as today */
+    merged.set(g.id, { ...g, ...roleFlags })
+  })
+  return Array.from(merged.values())
```

---

`4539-4581`: **Micro‑optimisation: recompute calls can be parallelised**

`for…of` recomputes sequentially; for large groups this can be slow.  
After wrapping in a transaction (see previous comment), you can safely `await Promise.all(…)` to run them concurrently.

Not critical for correctness, but worth a future enhancement.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 53366de28d5fdaa6876345848cb27d04861bf235 and d373e3eb11f6cab6191c3edf6ff83fce487e7dec.

</details>

<details>
<summary>⛔ Files ignored due to path filters (9)</summary>

* `packages/graphql/src/graphql/ops/MCreateUserGroup.graphql` is excluded by `!**/**/graphql/ops/**`
* `packages/graphql/src/graphql/ops/MDeleteUserGroup.graphql` is excluded by `!**/**/graphql/ops/**`
* `packages/graphql/src/graphql/ops/MLeaveUserGroup.graphql` is excluded by `!**/**/graphql/ops/**`
* `packages/graphql/src/graphql/ops/QGetUserGroupsUser.graphql` is excluded by `!**/**/graphql/ops/**`
* `packages/graphql/src/ops.schema.json` is excluded by `!**/**/ops.schema.json`
* `packages/graphql/src/ops.ts` is excluded by `!**/**/ops.ts`
* `packages/graphql/src/public/client.json` is excluded by `!**/**/public/**`
* `packages/graphql/src/public/schema.graphql` is excluded by `!**/**/public/**`
* `packages/graphql/src/public/server.json` is excluded by `!**/**/public/**`

</details>

<details>
<summary>📒 Files selected for processing (18)</summary>

* `apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/DeleteUserGroupSuccessToast.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/LeaveUserGroupErrorToast.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/LeaveUserGroupSuccessToast.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/UserGroupBadge.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx` (2 hunks)
* `apps/frontend-manage/src/components/groups/UserGroupEntry.tsx` (1 hunks)
* `apps/frontend-manage/src/components/groups/UserGroupList.tsx` (0 hunks)
* `apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx` (2 hunks)
* `cypress/cypress/e2e/L-catalog-workflow.cy.ts` (1 hunks)
* `packages/graphql/src/schema/mutation.ts` (1 hunks)
* `packages/graphql/src/schema/query.ts` (2 hunks)
* `packages/graphql/src/schema/sharing.ts` (2 hunks)
* `packages/graphql/src/services/sharing.ts` (1 hunks)
* `packages/i18n/messages/de.ts` (2 hunks)
* `packages/i18n/messages/en.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* apps/frontend-manage/src/components/groups/UserGroupList.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx (1)</summary><blockquote>

<details>
<summary>packages/graphql/src/services/sharing.ts (1)</summary>

* `deleteUserGroup` (4520-4585)

</details>

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupEntry.tsx (1)</summary><blockquote>

<details>
<summary>packages/graphql/src/schema/sharing.ts (1)</summary>

* `UserGroup` (175-192)

</details>

</blockquote></details>
<details>
<summary>packages/graphql/src/services/sharing.ts (2)</summary><blockquote>

<details>
<summary>packages/graphql/src/lib/context.ts (1)</summary>

* `ContextWithUser` (32-41)

</details>
<details>
<summary>packages/util/src/permissions.ts (1)</summary>

* `recomputeDerivedPermissions` (39-135)

</details>

</blockquote></details>
<details>
<summary>packages/graphql/src/schema/query.ts (1)</summary><blockquote>

<details>
<summary>packages/graphql/src/schema/sharing.ts (1)</summary>

* `UserGroup` (175-192)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: Analyze (javascript)
* GitHub Check: check
* GitHub Check: build
* GitHub Check: format
* GitHub Check: cypress-run
* GitHub Check: build
* GitHub Check: test
* GitHub Check: SonarCloud

</details>

<details>
<summary>🔇 Additional comments (28)</summary><blockquote>

<details>
<summary>cypress/cypress/e2e/L-catalog-workflow.cy.ts (1)</summary>

`809-812`: **LGTM! Placeholder for user group tests.**

Good preparation for upcoming end-to-end tests for the user group management functionality.

</details>
<details>
<summary>packages/graphql/src/schema/sharing.ts (2)</summary>

`170-172`: **Good addition of user role flags to the interface.**

These flags will be useful for determining the current user's relationship with each group.

---

`188-190`: **Proper implementation of role indicators in GraphQL type.**

The nullable Boolean fields correctly expose the role information to clients.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx (2)</summary>

`1-3`: **Appropriate imports for data fetching.**

The component now correctly imports Apollo Client's useQuery hook and the generated GraphQL document.



Also applies to: 7-7

---

`11-11`: **Proper implementation of GraphQL query.**

The component is now using Apollo's useQuery hook to fetch user groups data.

</details>
<details>
<summary>packages/graphql/src/schema/query.ts (1)</summary>

`1324-1330`: **LGTM! New query to fetch user groups.**

The implementation correctly:
1. Uses authentication to ensure only authorized users access the data
2. Returns a nullable list of UserGroup objects
3. Delegates to the SharingService for the actual implementation

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx (2)</summary>

`8-11`: **Clean import organization.**

The imports have been well organized, now including the `GetUserGroupsUserDocument` which is needed for the cache update functionality.

---

`77-93`: **Well-implemented cache update logic.**

The cache update implementation properly reads the existing user groups, appends the newly created one, and writes back to the cache. This ensures the UI stays in sync with the data without requiring a refetch.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/LeaveUserGroupErrorToast.tsx (1)</summary>

`1-27`: **Well-structured error toast component.**

This component follows React best practices with clear props typing and appropriate use of the Toast component from the design system. The duration (10 seconds) is appropriate for an error message, giving users sufficient time to read it.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx (1)</summary>

`1-27`: **Clean error toast implementation.**

This component mirrors the structure of the LeaveUserGroupErrorToast with appropriate translation keys for delete operations. The consistency in implementation between these related components is good for maintainability.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/DeleteUserGroupSuccessToast.tsx (1)</summary>

`1-27`: **Appropriately configured success toast.**

This success toast follows the same pattern as the error toasts but with appropriate adjustments - using the success type and a shorter duration (3 seconds vs 10 seconds for errors). This is a good UX decision as success messages generally don't need to be displayed as long as error messages.

</details>
<details>
<summary>packages/i18n/messages/de.ts (2)</summary>

`2408-2408`: **Consistent naming addition for pluralization.**

The addition of the plural "members" key complements the existing singular "member" key, maintaining proper i18n pluralization patterns.

---

`2421-2430`: **Good set of translation keys for user group management.**

These translation keys provide comprehensive coverage for the user group management functionality, including viewing, editing, leaving, and deleting groups, with appropriate success and error messages.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/LeaveUserGroupSuccessToast.tsx (4)</summary>

`1-3`: **Clean imports.**

The component imports only what it needs - the Toast component from the design system and the translation hook from next-intl.

---

`4-11`: **Well-typed props interface.**

The component has explicit TypeScript typing for its props, making it clear what inputs it expects.

---

`13-24`: **Clean toast implementation.**

The Toast component is well-configured with appropriate props:
- dismissible for user control
- success type for correct semantic meaning
- external open state management
- className for styling
- 3000ms duration for good UX

---

`27-27`: **Standard export pattern.**

The component follows the standard pattern of default exporting the component.

</details>
<details>
<summary>packages/i18n/messages/en.ts (7)</summary>

`2379-2379`: **Check plural key consistency**  
The new `members` key aligns with the existing singular `member` key, ensuring consistent labeling for list views.

---

`2391-2394`: **Action labels follow convention**  
The new `viewGroup`, `editGroup`, `leaveGroup`, and `deleteGroup` keys use the established `Verb + User Group` pattern and mirror similar labels elsewhere.

---

`2397-2397`: **Approve success message**  
The `leaveGroupSuccess` message is clear and correctly reflects the action outcome.

---

`2400-2400`: **Approve success message**  
The `deleteGroupSuccess` message is concise and consistent with other success labels.

---

`2403-2404`: **Approve deletion confirmation**  
The `confirmDeleteGroup` prompt clearly communicates the consequences and includes `{groupName}` in quotes for emphasis.

---

`2405-2406`: **Approve resolution confirmation**  
The `resolveGroupConfirmation` message accurately describes the effect of resolving (clearing) the group.

---

`2409-2410`: **Approve irreversible action notice**  
The `irreversibleActionConfirmation` warning is clear and matches the tone of other irreversible-action prompts.

</details>
<details>
<summary>packages/graphql/src/schema/mutation.ts (2)</summary>

`1567-1574`: **LGTM! New mutation for leaving a user group.**

The implementation correctly defines a GraphQL mutation that allows users with full access to leave a user group. The mutation accepts a groupId parameter and returns a boolean indicating success or failure.

---

`1576-1583`: **LGTM! New mutation for deleting a user group.**

The implementation correctly defines a GraphQL mutation that allows users with full access to delete a user group. The mutation accepts a groupId parameter and returns a boolean indicating success or failure.

</details>
<details>
<summary>apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx (1)</summary>

`120-133`: **Cache update is brittle – missing query variables & misleading local name**

`cache.readQuery({ query: GetUserGroupsUserDocument })` omits the variables object that was probably used when the list was fetched (e.g. pagination, language,…).  
If the query was executed with variables, the read will silently return `null`, breaking the optimistic cache update.

Also, variable `catalogObjects` is a misleading name for a user‑group list.

Consider:

```diff
-                  const catalogObjects = cache.readQuery({
+                  const existingGroups = cache.readQuery({
                     query: GetUserGroupsUserDocument,
+                    // variables: { …sameAsQuery }
                   })
-                  if (catalogObjects?.getUserGroupsUser) {
+                  if (existingGroups?.getUserGroupsUser) {
                     cache.writeQuery({
                       query: GetUserGroupsUserDocument,
-                      data: { getUserGroupsUser: catalogObjects?.getUserGroupsUser.filter((group) => group.id !== groupId) }
+                      data: {
+                        getUserGroupsUser: existingGroups.getUserGroupsUser.filter(
+                          (group) => group.id !== groupId
+                        ),
+                      },
+                      // variables: { …sameAsQuery }
                     })
                   }
```

</details>
<details>
<summary>packages/graphql/src/services/sharing.ts (1)</summary>

`4343-4348`: **`isMember` / `isAdmin` always `false` for owner – confirm intended UX**

`createUserGroup` sets the flags

```ts
isMember: false,
isAdmin: false,
isOwner: true,
```

for the creator, which means the owner is considered *neither* member nor admin in the API response.  
Down‑stream UI often expects the owner implicitly to be both. Double‑check that lists, counters, or permission checks don’t rely on `isMember || isAdmin` for owners before shipping.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-23T08:13:17Z

Review comments:
diff_hunk: @@ -2387,6 +2388,26 @@ Since the KlickerUZH app is not yet available on the iOS App Store, follow these
       creationSuccessMessage: 'The user group was created successfully.',
       creationErrorMessage:
         'An error occurred while creating the user group. Please ensure that at information for at least one valid member (not yourself) was provided and that no other user group with the same name already exists.',
+      viewGroup: 'View User Group',
+      editGroup: 'Edit User Group',
+      leaveGroup: 'Leave User Group',
+      deleteGroup: 'Delete User Group',
+      confirmLeaveGroup:
+        'You are about to leave the user group {groupName}. By leaving this group, you will loose access to all objects that are shared with this group. If you are sure, please confirm this action.',
path: packages/i18n/messages/en.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix spelling in confirmation prompt**  
There’s a typo: “loose access” should be “lose access.”  
  
Apply this diff:
```diff
- 'You are about to leave the user group {groupName}. By leaving this group, you will loose access to all objects that are shared with this group. If you are sure, please confirm this action.',
+ 'You are about to leave the user group {groupName}. By leaving this group, you will lose access to all objects that are shared with this group. If you are sure, please confirm this action.',
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      confirmLeaveGroup:
        'You are about to leave the user group {groupName}. By leaving this group, you will lose access to all objects that are shared with this group. If you are sure, please confirm this action.',
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:15Z
author_association: NONE
diff_hunk: @@ -2387,6 +2388,26 @@
       creationSuccessMessage: 'The user group was created successfully.',
       creationErrorMessage:
         'An error occurred while creating the user group. Please ensure that at information for at least one valid member (not yourself) was provided and that no other user group with the same name already exists.',
+      viewGroup: 'View User Group',
+      editGroup: 'Edit User Group',
+      leaveGroup: 'Leave User Group',
+      deleteGroup: 'Delete User Group',
+      confirmLeaveGroup:
+        'You are about to leave the user group {groupName}. By leaving this group, you will loose access to all objects that are shared with this group. If you are sure, please confirm this action.',
+      leaveGroupSuccess: 'You have successfully left the user group.',
+      leaveGroupError:
+        'An error occurred while leaving the user group. Please try again or contact the support.',
+      deleteGroupSuccess: 'You have successfully deleted the user group.',
+      deleteGroupError:
+        'An error occurred while deleting the user group. Please try again or contact the support.',
+      confirmDeleteGroup:
+        'Please review the effects of deleting this user group "{groupName}" carefully and confirm them before finalizing the deletion.',
+      resolveGroupConfirmation:
+        'By resolving this group, all members and admins will be removed from it.',
+      revokeDirectPermissionsConfirmation:
+        'All direct permissions granted to the group will be revoked, all group members will loose access to the corresponding objects.',
path: packages/i18n/messages/en.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix spelling in permissions revocation prompt**  
Another typo needs correction: “loose access” → “lose access.”  
  
Apply this diff:
```diff
- 'All direct permissions granted to the group will be revoked, all group members will loose access to the corresponding objects.',
+ 'All direct permissions granted to the group will be revoked, all group members will lose access to the corresponding objects.',
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      revokeDirectPermissionsConfirmation:
        'All direct permissions granted to the group will be revoked, all group members will lose access to the corresponding objects.',
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,103 @@
+import { useMutation } from '@apollo/client'
+import {
+  faBan,
+  faPersonWalkingArrowRight,
+} from '@fortawesome/free-solid-svg-icons'
+import {
+  GetUserGroupsUserDocument,
+  LeaveUserGroupDocument,
+} from '@klicker-uzh/graphql/dist/ops'
+import { Button, Modal } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+import { useState } from 'react'
+import LeaveUserGroupErrorToast from './LeaveUserGroupErrorToast'
+
+function LeaveUserGroupModal({
+  open,
+  onClose,
+  onSuccess,
+  groupId,
+  groupName,
+}: {
+  open: boolean
+  onClose: () => void
+  onSuccess: () => void
+  groupId: number
+  groupName: string
+}) {
+  const t = useTranslations()
+  const [errorToast, setErrorToast] = useState(false)
+  const [leaveUserGroup] = useMutation(LeaveUserGroupDocument)
+
+  return (
+    <Modal
+      open={open}
+      onClose={onClose}
+      title={t('manage.userGroups.leaveGroup')}
+      className={{ content: '!max-w-xl' }}
+    >
+      <div className="mb-3">
+        {t('manage.userGroups.confirmLeaveGroup', { groupName })}
+      </div>
+      <div className="flex flex-row justify-between">
+        <Button>
+          <Button.Icon icon={faBan} />
+          <Button.Label>{t('shared.generic.cancel')}</Button.Label>
+        </Button>
path: apps/frontend-manage/src/components/groups/LeaveUserGroupModal.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add missing click handler to Cancel button.**

The Cancel button doesn't have an onClick handler to close the modal when clicked.

Add the onClose handler to the Cancel button:

```diff
<Button
+ onClick={onClose}
>
  <Button.Icon icon={faBan} />
  <Button.Label>{t('shared.generic.cancel')}</Button.Label>
</Button>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        <Button onClick={onClose}>
          <Button.Icon icon={faBan} />
          <Button.Label>{t('shared.generic.cancel')}</Button.Label>
        </Button>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,159 @@
+import { useMutation } from '@apollo/client'
+import { faTrashCan } from '@fortawesome/free-regular-svg-icons'
+import { faBan } from '@fortawesome/free-solid-svg-icons'
+import {
+  DeleteUserGroupDocument,
+  GetUserGroupsUserDocument,
+} from '@klicker-uzh/graphql/dist/ops'
+import { Button, Modal } from '@uzh-bf/design-system'
+import { useTranslations } from 'next-intl'
+import { useEffect, useState } from 'react'
+import ConfirmationItem from '../common/ConfirmationItem'
+import DeleteUserGroupErrorToast from './DeleteUserGroupErrorToast'
+
+function DeleteUserGroupModal({
+  open,
+  onClose,
+  onSuccess,
+  groupId,
+  groupName,
+}: {
+  open: boolean
+  onClose: () => void
+  onSuccess: () => void
+  groupId: number
+  groupName: string
+}) {
+  const t = useTranslations()
+  const [deleteUserGroup] = useMutation(DeleteUserGroupDocument)
+
+  const [errorToast, setErrorToast] = useState(false)
+  const [confirmations, setConfirmations] = useState({
+    resolveGroup: false,
+    revokeDirectPermissions: false,
+    irreversibleAction: false,
+  })
+
+  // on open, reset confirmations
+  useEffect(() => {
+    if (open) {
+      setConfirmations({
+        resolveGroup: false,
+        revokeDirectPermissions: false,
+        irreversibleAction: false,
+      })
+    }
+  }, [open])
+
+  return (
+    <Modal
+      open={open}
+      onClose={onClose}
+      title={t('manage.userGroups.deleteGroup')}
+      className={{ content: '!max-w-2xl' }}
+    >
+      <div className="mb-3">
+        {t('manage.userGroups.confirmDeleteGroup', { groupName })}
+      </div>
+
+      <div className="mb-2 flex flex-col gap-2">
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.resolveGroup}
+          label={t('manage.userGroups.resolveGroupConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              resolveGroup: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-resolve-group-confirm' }}
+        />
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.revokeDirectPermissions}
+          label={t('manage.userGroups.revokeDirectPermissionsConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              revokeDirectPermissions: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-revoke-permissions-confirm' }}
+        />
+        <ConfirmationItem
+          notApplicable={false}
+          confirmed={confirmations.irreversibleAction}
+          label={t('manage.userGroups.irreversibleActionConfirmation')}
+          onClick={() => {
+            setConfirmations((prev) => ({
+              ...prev,
+              irreversibleAction: true,
+            }))
+          }}
+          confirmationType="delete"
+          data={{ cy: 'delete-group-irrevocable-action-confirm' }}
+        />
+      </div>
+
+      <div className="flex flex-row justify-between">
+        <Button>
+          <Button.Icon icon={faBan} />
+          <Button.Label>{t('shared.generic.cancel')}</Button.Label>
+        </Button>
path: apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Cancel button does nothing – missing `onClick={onClose}` handler**

Users cannot close the modal via the “Cancel” button because no click handler is wired up.  
Add the handler (and consider `type="button"` to avoid accidental form submission).

```diff
-        <Button>
+        <Button onClick={onClose} type="button">
           <Button.Icon icon={faBan} />
           <Button.Label>{t('shared.generic.cancel')}</Button.Label>
         </Button>
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      <div className="flex flex-row justify-between">
-        <Button>
+        <Button onClick={onClose} type="button">
           <Button.Icon icon={faBan} />
           <Button.Label>{t('shared.generic.cancel')}</Button.Label>
         </Button>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:16Z
author_association: NONE
diff_hunk: @@ -4342,6 +4342,246 @@ export async function createUserGroup(
   return {
     ...newUserGroup,
     numOfMembers: newUserGroup.members.length + newUserGroup.admins.length + 1,
+    isMember: false,
+    isAdmin: false,
+    isOwner: true,
   }
 }
+
+export async function getUserGroupsUser(ctx: ContextWithUser) {
+  const user = await ctx.prisma.user.findUnique({
+    where: { id: ctx.user.sub },
+    include: {
+      // user is MEMBER
+      userGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is ADMIN
+      adminUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is OWNER
+      managedUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+    },
+  })
+
+  if (!user) {
+    return null
+  }
+
+  return [
+    ...user.userGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: true,
+      isAdmin: false,
+      isOwner: false,
+    })),
+    ...user.adminUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: true,
+      isOwner: false,
+    })),
+    ...user.managedUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: false,
+      isOwner: true,
+    })),
+  ]
+}
+
+// TODO: add unit testing for this function
+export async function leaveUserGroup(
+  { groupId }: { groupId: number },
+  ctx: ContextWithUser
+) {
+  // check if the user is a member or admin of the group
+  const userGroup = await ctx.prisma.userGroup.findUnique({
+    where: { id: groupId },
+    include: {
+      members: true,
+      admins: true,
+    },
+  })
+
+  if (
+    !userGroup ||
+    (userGroup.members.length === 0 && userGroup.admins.length === 0)
+  ) {
+    return false
+  }
+
+  // remove the user from the group
+  const updatedUserGroup = await ctx.prisma.userGroup.update({
+    where: { id: groupId },
+    data: {
+      members:
+        userGroup.members.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+      admins:
+        userGroup.admins.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+    },
+    include: {
+      permissions: true,
+    },
+  })
+
+  // trigger a derived permission recomputation for all objects that were shared with this group and this user
+  for (const permission of updatedUserGroup.permissions) {
+    if (permission.catalogCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          catalogCollectionId: permission.catalogCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.answerCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          answerCollectionId: permission.answerCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.elementId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          elementId: permission.elementId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.courseId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          courseId: permission.courseId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.liveQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          liveQuizId: permission.liveQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.practiceQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          practiceQuizId: permission.practiceQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.microLearningId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          microLearningId: permission.microLearningId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.groupActivityId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          groupActivityId: permission.groupActivityId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    }
+  }
+
+  return true
+}
+
+// TODO: add unit testing for this function
+export async function deleteUserGroup(
+  { groupId }: { groupId: number },
+  ctx: ContextWithUser
+) {
+  // check if the user is the owner of the group
+  const userGroup = await ctx.prisma.userGroup.findUnique({
+    where: { id: groupId, ownerId: ctx.user.sub },
+    include: { permissions: true },
+  })
+
+  if (!userGroup) {
+    return false
+  }
+
+  // delete the user group
+  await ctx.prisma.userGroup.delete({
+    where: { id: groupId },
+  })
path: packages/graphql/src/services/sharing.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Wrap deletion + recomputations in a single transaction**

`deleteUserGroup` deletes the group first, then recomputes permissions in a separate loop.  
If the process crashes between those two steps, the group is gone but derived permissions stay stale.

Encapsulate both operations in `ctx.prisma.$transaction(async prisma => { … })` and pass the transactional `prisma` client to `recomputeDerivedPermissions`.

```diff
-  // delete the user group
-  await ctx.prisma.userGroup.delete({ where: { id: groupId } })
-
-  // recompute …
+  await ctx.prisma.$transaction(async prisma => {
+    await prisma.userGroup.delete({ where: { id: groupId } })
+    for (const permission of userGroup.permissions) {
+      // … call recomputeDerivedPermissions with `prisma`
+    }
+  })
```



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:16Z
author_association: NONE
diff_hunk: @@ -4342,6 +4342,246 @@
   return {
     ...newUserGroup,
     numOfMembers: newUserGroup.members.length + newUserGroup.admins.length + 1,
+    isMember: false,
+    isAdmin: false,
+    isOwner: true,
   }
 }
+
+export async function getUserGroupsUser(ctx: ContextWithUser) {
+  const user = await ctx.prisma.user.findUnique({
+    where: { id: ctx.user.sub },
+    include: {
+      // user is MEMBER
+      userGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is ADMIN
+      adminUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+      // user is OWNER
+      managedUserGroups: {
+        include: {
+          members: true,
+          admins: true,
+        },
+      },
+    },
+  })
+
+  if (!user) {
+    return null
+  }
+
+  return [
+    ...user.userGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: true,
+      isAdmin: false,
+      isOwner: false,
+    })),
+    ...user.adminUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: true,
+      isOwner: false,
+    })),
+    ...user.managedUserGroups.map((group) => ({
+      ...group,
+      numOfMembers: group.admins.length + group.members.length + 1,
+      isMember: false,
+      isAdmin: false,
+      isOwner: true,
+    })),
+  ]
+}
+
+// TODO: add unit testing for this function
+export async function leaveUserGroup(
+  { groupId }: { groupId: number },
+  ctx: ContextWithUser
+) {
+  // check if the user is a member or admin of the group
+  const userGroup = await ctx.prisma.userGroup.findUnique({
+    where: { id: groupId },
+    include: {
+      members: true,
+      admins: true,
+    },
+  })
+
+  if (
+    !userGroup ||
+    (userGroup.members.length === 0 && userGroup.admins.length === 0)
+  ) {
+    return false
+  }
+
+  // remove the user from the group
+  const updatedUserGroup = await ctx.prisma.userGroup.update({
+    where: { id: groupId },
+    data: {
+      members:
+        userGroup.members.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+      admins:
+        userGroup.admins.length > 0
+          ? { disconnect: { id: ctx.user.sub } }
+          : undefined,
+    },
+    include: {
+      permissions: true,
+    },
+  })
+
+  // trigger a derived permission recomputation for all objects that were shared with this group and this user
+  for (const permission of updatedUserGroup.permissions) {
+    if (permission.catalogCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          catalogCollectionId: permission.catalogCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.answerCollectionId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          answerCollectionId: permission.answerCollectionId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.elementId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          elementId: permission.elementId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.courseId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          courseId: permission.courseId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.liveQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          liveQuizId: permission.liveQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.practiceQuizId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          practiceQuizId: permission.practiceQuizId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.microLearningId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          microLearningId: permission.microLearningId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    } else if (permission.groupActivityId !== null) {
+      await recomputeDerivedPermissions(
+        {
+          groupActivityId: permission.groupActivityId,
+          userId: ctx.user.sub,
+        },
+        ctx.prisma
+      )
+    }
+  }
+
+  return true
+}
path: packages/graphql/src/services/sharing.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`leaveUserGroup` lacks membership check – any logged‑in user can call it**

The only validation is that the group has *some* members/admins (lines 4422‑4426).  
A user not belonging to the group at all could successfully call `leaveUserGroup` and get `true`, potentially triggering needless permission recomputation.

Patch:

```diff
-  if (
-    !userGroup ||
-    (userGroup.members.length === 0 && userGroup.admins.length === 0)
-  ) {
+  const isMember   = userGroup?.members.some(u => u.id === ctx.user.sub)
+  const isAdmin    = userGroup?.admins.some(u => u.id === ctx.user.sub)
+
+  if (!userGroup || (!isMember && !isAdmin)) {
     return false
   }
```

Also, when building the `disconnect` object, use `isMember`/`isAdmin` instead of collection length to avoid superfluous `disconnect` calls.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export async function leaveUserGroup(
  { groupId }: { groupId: number },
  ctx: ContextWithUser
) {
  // check if the user is a member or admin of the group
  const userGroup = await ctx.prisma.userGroup.findUnique({
    where: { id: groupId },
    include: {
      members: true,
      admins: true,
    },
  })

  const isMember = userGroup?.members.some(u => u.id === ctx.user.sub)
  const isAdmin  = userGroup?.admins.some(u => u.id === ctx.user.sub)

  if (!userGroup || (!isMember && !isAdmin)) {
    return false
  }

  // remove the user from the group
  const updatedUserGroup = await ctx.prisma.userGroup.update({
    where: { id: groupId },
    data: {
      members:
        userGroup.members.length > 0
          ? { disconnect: { id: ctx.user.sub } }
          : undefined,
      admins:
        userGroup.admins.length > 0
          ? { disconnect: { id: ctx.user.sub } }
          : undefined,
    },
    include: {
      permissions: true,
    },
  })

  // trigger a derived permission recomputation for all objects that were shared...
  for (const permission of updatedUserGroup.permissions) {
    // ...
  }

  return true
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-23T08:13:16Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-23T08:09:39Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This update introduces comprehensive user group management functionality to the application. It adds GraphQL queries and mutations for listing, leaving, and deleting user groups, with corresponding backend service logic and schema changes. The frontend is expanded with new React components for displaying user groups, managing group membership, and handling user interactions through modals and toast notifications. Localization files are updated to support new user interface messages for group actions. The user group list is now dynamically rendered based on data fetched from the server, replacing the previous static placeholder. Additionally, a placeholder for related end-to-end tests is added.

## Changes

| File(s)                                                                                      | Change Summary |
|----------------------------------------------------------------------------------------------|---------------|
| `apps/frontend-manage/src/components/groups/DeleteUserGroupErrorToast.tsx`,<br>`DeleteUserGroupSuccessToast.tsx`,<br>`LeaveUserGroupErrorToast.tsx`,<br>`LeaveUserGroupSuccessToast.tsx` | Added new React components for displaying error and success toast notifications related to deleting and leaving user groups. |
| `apps/frontend-manage/src/components/groups/DeleteUserGroupModal.tsx`,<br>`LeaveUserGroupModal.tsx` | Added new modal components for confirming and executing user group deletion and leave actions, including confirmation flows and error handling. |
| `apps/frontend-manage/src/components/groups/UserGroupBadge.tsx` | Added a badge component to visually indicate user roles (member, admin, owner) within a group. |
| `apps/frontend-manage/src/components/groups/UserGroupEntry.tsx` | Introduced a component to render user group details, actions, and manage modals/toasts for group actions. |
| `apps/frontend-manage/src/components/groups/UserGroupCreationForm.tsx` | Enabled Apollo cache updates after creating a user group by updating the relevant query cache. |
| `apps/frontend-manage/src/components/groups/UserGroupsManagement.tsx` | Updated to fetch and render user groups dynamically from the server, replacing static display logic. |
| `apps/frontend-manage/src/components/groups/UserGroupList.tsx` | Deleted obsolete static user group list component. |
| `cypress/cypress/e2e/L-catalog-workflow.cy.ts` | Added a placeholder section for future user group workflow tests. |
| `packages/graphql/src/schema/mutation.ts` | Added `leaveUserGroup` and `deleteUserGroup` mutations to the GraphQL schema. |
| `packages/graphql/src/schema/query.ts` | Added `getUserGroupsUser` query to fetch user groups for the current user. |
| `packages/graphql/src/schema/sharing.ts` | Extended `IUserGroup` and `UserGroup` types with `isMember`, `isAdmin`, and `isOwner` fields. |
| `packages/graphql/src/services/sharing.ts` | Implemented backend logic for listing, leaving, and deleting user groups; updated group creation to include role flags. |
| `packages/i18n/messages/en.ts`,<br>`de.ts` | Added localization strings for user group actions, confirmations, and feedback messages. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant UI as Frontend UI
    participant GQL as GraphQL API
    participant Service as Sharing Service

    User->>UI: Views user groups
    UI->>GQL: getUserGroupsUser query
    GQL->>Service: getUserGroupsUser(ctx)
    Service-->>GQL: List of user groups with roles
    GQL-->>UI: User groups data
    UI-->>User: Displays user groups

    User->>UI: Initiates leave or delete group
    UI->>GQL: leaveUserGroup/deleteUserGroup mutation (groupId)
    GQL->>Service: leaveUserGroup/deleteUserGroup({groupId}, ctx)
    Service-->>GQL: Boolean (success/failure)
    GQL-->>UI: Mutation result
    UI-->>User: Shows success or error toast
```

## Suggested labels

`enhancement`, `size:XL`

## Suggested reviewers

- rschlaefli

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=uzh-bf/klicker-uzh&utm_content=4638):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-23T08:11:51Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=uzh-bf_klicker-uzh&pullRequest=4638) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [20 New issues](https://sonarcloud.io/project/issues?id=uzh-bf_klicker-uzh&pullRequest=4638&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=uzh-bf_klicker-uzh&pullRequest=4638&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=uzh-bf_klicker-uzh&pullRequest=4638&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=uzh-bf_klicker-uzh&pullRequest=4638&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.2% Duplication on New Code](https://sonarcloud.io/component_measures?id=uzh-bf_klicker-uzh&pullRequest=4638&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=uzh-bf_klicker-uzh&pullRequest=4638)


author_association: NONE
user: login: cypress[bot]
created_at: 2025-04-23T08:54:23Z
body: 
<h3>
  <a href="https://cloud.cypress.io/projects/y436dx?utm_source=github&utm_medium=PASSED&utm_campaign=project">klicker-uzh</a>&nbsp;
  <img src="https://assets.cypress.io/github-pr-comment-icons/chevron.svg" alt="" height="14" width="14" />&nbsp;
  <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">Run #4949</a>
</h3>

<details>
  <summary>
    <strong>Run Properties:</strong>&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#4949</a></code> &nbsp;&bull;&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/uzh-bf/klicker-uzh/commit/a03f1216532fe7c4cb8eb91f0cbe596d7f7e95b0">a03f121653 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span></a>: Merge d373e3eb11f6cab6191c3edf6ff83fce487e7dec into 53366de28d5fdaa6876345848cb2...</code>
  </summary><br />

  <table width="744">
    <tbody>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Project</strong>
        </td>
        <td width="520">
          <code><a href="https://cloud.cypress.io/projects/y436dx?utm_source=github&utm_medium=PASSED&utm_campaign=project">klicker-uzh</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Branch Review</strong>
        </th>
        <td>
          <code><a href="https://cloud.cypress.io/projects/y436dx/branches/use-group-manipulation/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=4638">use-group-manipulation</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run status</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#4949</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run duration</strong></td>
        <td><code>35m 06s</code></td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Commit</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/uzh-bf/klicker-uzh/commit/a03f1216532fe7c4cb8eb91f0cbe596d7f7e95b0">a03f121653 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span></a>: Merge d373e3eb11f6cab6191c3edf6ff83fce487e7dec into 53366de28d5fdaa6876345848cb2...</code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Committer</strong></td>
        <td><code>Julius Schlapbach</code></td>
      </tr>
    </tbody>
    <tfoot>
      <tr />
      <tr height="48">
        <td colspan="2">
          <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/properties?utm_source=github&utm_medium=PASSED&utm_campaign=properties">View all properties for this run ↗︎</a>
        </td>
      </tr>
    </tfoot>
  </table>
  <hr />
</details>

<table width="744">
  <thead>
    <tr />
    <tr height="48">
      <th colspan="2" align="left" width="744">Test results</th>
    </tr>
  </thead>
  <tbody>
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/failed.svg" alt="Tests that failed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?reviewViewBy=FAILED&utm_source=github&utm_medium=PASSED&utm_campaign=failed%20tests">
          <strong>Failures</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/flaky-v3.svg" alt="Tests that were flaky" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?reviewViewBy=FLAKY&utm_source=github&utm_medium=PASSED&utm_campaign=flaky%20tests">
          <strong>Flaky</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/pending.svg" alt="Tests that did not run due to a developer annotating a test with .skip" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=pending%20tests&statuses=%5B%7B%22value%22%3A%22PENDING%22%2C%22label%22%3A%22PENDING%22%7D%5D">
          <strong>Pending</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/skipped-v3.svg" alt="Tests that did not run due to a failure in a mocha hook" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=skipped%20tests&statuses=%5B%7B%22value%22%3A%22SKIPPED%22%2C%22label%22%3A%22SKIPPED%22%7D%5D">
          <strong>Skipped</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="Tests that passed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/4949/overview?utm_source=github&utm_medium=PASSED&utm_campaign=passed%20tests">
          <strong>Passing</strong>
        </a>
      </td>
      <td>
        <code><strong>499</strong></code>
      </td>
    </tr><tfoot>
    <tr height="48"><td colspan="2"><a href="https://cloud.cypress.io/projects/y436dx/branches/use-group-manipulation/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=4638">View all changes introduced in this branch ↗︎</a></td></tr></tfoot>
  </tbody>
</table>






author_association: NONE