@@ -367,7 +392,7 @@ export default async function Page() {
quality={100}
src={image20251013rolesHistory}
alt=""
- loading="eager"
+ loading="lazy"
/>
diff --git a/app/src/app/app/layout.tsx b/app/src/app/app/layout.tsx
index 207bd5566..4a65befeb 100644
--- a/app/src/app/app/layout.tsx
+++ b/app/src/app/app/layout.tsx
@@ -11,6 +11,8 @@ import { ServiceWorkerLoader } from "@/modules/common/components/ServiceWorkerLo
import { getUnleashFlag } from "@/modules/common/utils/getUnleashFlag";
import { UNLEASH_FLAG } from "@/modules/common/utils/UNLEASH_FLAG";
import { ChannelsProvider } from "@/modules/pusher/components/ChannelsContext";
+import { RolesContextProvider } from "@/modules/roles/components/RolesContext";
+import { getVisibleRoles } from "@/modules/roles/utils/getRoles";
import { CmdKProvider } from "@/modules/shell/components/CmdK/CmdKContext";
import { MobileActionBarLoader } from "@/modules/shell/components/Sidebar/MobileActionBarLoader";
import { TopBar } from "@/modules/shell/components/TopBar";
@@ -21,11 +23,13 @@ import { NuqsAdapter } from "nuqs/adapters/next/app";
import { Suspense } from "react";
export default async function AppLayout({ children }: LayoutProps<"/app">) {
- const [authentication, disableAlgolia, apps] = await Promise.all([
- requireAuthenticationPage(),
- getUnleashFlag(UNLEASH_FLAG.DisableAlgolia),
- getAppLinks(),
- ]);
+ const [authentication, disableAlgolia, apps, visibleRoles] =
+ await Promise.all([
+ requireAuthenticationPage(),
+ getUnleashFlag(UNLEASH_FLAG.DisableAlgolia),
+ getAppLinks(),
+ getVisibleRoles(),
+ ]);
return (
<>
@@ -35,34 +39,36 @@ export default async function AppLayout({ children }: LayoutProps<"/app">) {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
- {children}
-
-
-
-
+
+ {children}
+
+
+
+
-
-
-
+
+
+
- {authentication.session.user.role === "admin" && (
-
- )}
+ {authentication.session.user.role === "admin" && (
+
+ )}
-
+
+
diff --git a/app/src/app/app/leaderboards/page.tsx b/app/src/app/app/leaderboards/page.tsx
index 97619f9c2..63f1112fe 100644
--- a/app/src/app/app/leaderboards/page.tsx
+++ b/app/src/app/app/leaderboards/page.tsx
@@ -46,11 +46,11 @@ export default async function Page() {
{citizen.rank}
-
+
{/* */}
{citizen.displayname}
{/* */}
diff --git a/app/src/app/app/roles/[id]/inheritance/page.tsx b/app/src/app/app/roles/[id]/inheritance/page.tsx
index c0499d3ed..28206ff38 100644
--- a/app/src/app/app/roles/[id]/inheritance/page.tsx
+++ b/app/src/app/app/roles/[id]/inheritance/page.tsx
@@ -1,6 +1,6 @@
import { requireAuthenticationPage } from "@/modules/auth/server";
import { InheritanceForm } from "@/modules/roles/components/InheritanceForm";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { getRoleById, getRoles } from "@/modules/roles/queries";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
@@ -30,7 +30,8 @@ export default async function Page({
Vererbungen
- Die Rolle {" "}
+ Die Rolle{" "}
+ {" "}
erhält alle Berechtigungen von den folgenden ausgewählten Rollen. Im
Karrieresystem gelten die folgenden Rollen ebenfalls als freigeschaltet.
Verschachtelte Vererbungen werden nicht berücksichtigt.
diff --git a/app/src/app/app/spynet/citizen/[id]/layout.tsx b/app/src/app/app/spynet/citizen/[id]/layout.tsx
index cc1bdae71..9e7d034b9 100644
--- a/app/src/app/app/spynet/citizen/[id]/layout.tsx
+++ b/app/src/app/app/spynet/citizen/[id]/layout.tsx
@@ -35,9 +35,7 @@ export default async function Layout({
/
-
- {citizen.handle || citizen.id}
-
+ {citizen.handle || citizen.id}
diff --git a/app/src/app/app/spynet/organization/[id]/page.tsx b/app/src/app/app/spynet/organization/[id]/page.tsx
index c01e5d82b..ec211c6ec 100644
--- a/app/src/app/app/spynet/organization/[id]/page.tsx
+++ b/app/src/app/app/spynet/organization/[id]/page.tsx
@@ -71,9 +71,7 @@ export default async function Page(props: Props) {
/
-
- {organization.name}
-
+ {organization.name}
{/* {authentication.authorize("organization", "delete") && } */}
diff --git a/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx b/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx
index d3b8a4ccf..55dfa4588 100644
--- a/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx
+++ b/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx
@@ -2,7 +2,7 @@
import { Handles } from "@/modules/career/components/Handles";
import { CitizenLink } from "@/modules/common/components/CitizenLink";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import {
FlowNodeRoleCitizensAlignment,
FlowNodeType,
@@ -231,8 +231,8 @@ export const Node: ComponentType> = (props) => {
})}
>
{!props.data.roleCitizensHideRole && (
-
)}
diff --git a/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png b/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png
new file mode 100644
index 000000000..122c4900b
Binary files /dev/null and b/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png differ
diff --git a/app/src/modules/citizen/actions/deleteRoleAssignment.ts b/app/src/modules/citizen/actions/deleteRoleAssignment.ts
new file mode 100644
index 000000000..ff5820636
--- /dev/null
+++ b/app/src/modules/citizen/actions/deleteRoleAssignment.ts
@@ -0,0 +1,68 @@
+"use server";
+
+import { prisma } from "@/db";
+import { createAuthenticatedAction } from "@/modules/actions/utils/createAction";
+import { RoleAssignmentChangeType } from "@prisma/client";
+import { refresh } from "next/cache";
+import { z } from "zod";
+
+const schema = z.object({
+ citizenId: z.string(),
+ roleId: z.string(),
+});
+
+export const deleteRoleAssignment = createAuthenticatedAction(
+ "deleteRoleAssignment",
+ schema,
+ async (formData, authentication, data, t) => {
+ /**
+ * Authorize the request
+ */
+ if (!authentication.session.entity)
+ return {
+ error: t("Common.forbidden"),
+ requestPayload: formData,
+ };
+ if (
+ !(await authentication.authorize("otherRole", "dismiss", [
+ {
+ key: "roleId",
+ value: data.roleId,
+ },
+ ]))
+ )
+ return {
+ error: t("Common.forbidden"),
+ requestPayload: formData,
+ };
+
+ /**
+ *
+ */
+ await prisma.$transaction([
+ prisma.roleAssignment.delete({
+ where: {
+ citizenId_roleId: {
+ citizenId: data.citizenId,
+ roleId: data.roleId,
+ },
+ },
+ }),
+
+ prisma.roleAssignmentChange.create({
+ data: {
+ citizenId: data.citizenId,
+ roleId: data.roleId,
+ type: RoleAssignmentChangeType.REMOVE,
+ createdById: authentication.session.entity.id,
+ },
+ }),
+ ]);
+
+ refresh();
+
+ return {
+ success: t("Common.successfullySaved"),
+ };
+ },
+);
diff --git a/app/src/modules/citizen/actions/updateRoleAssignment.ts b/app/src/modules/citizen/actions/updateRoleAssignment.ts
index 1238aff55..61b53e6ea 100644
--- a/app/src/modules/citizen/actions/updateRoleAssignment.ts
+++ b/app/src/modules/citizen/actions/updateRoleAssignment.ts
@@ -5,7 +5,6 @@ import { createAuthenticatedAction } from "@/modules/actions/utils/createAction"
import { triggerNotifications } from "@/modules/notifications/utils/triggerNotification";
import { getRoles } from "@/modules/roles/queries";
import { RoleAssignmentChangeType } from "@prisma/client";
-import { revalidatePath } from "next/cache";
import { z } from "zod";
export interface Change {
@@ -155,11 +154,6 @@ export const updateRoleAssignments = createAuthenticatedAction(
})),
);
- /**
- * Revalidate cache(s)
- */
- revalidatePath("/app/account/notifications");
-
return {
success: t("Common.successfullySaved"),
};
diff --git a/app/src/modules/citizen/components/CitizenInput.tsx b/app/src/modules/citizen/components/CitizenInput.tsx
index b30a97fdd..516ddf74a 100644
--- a/app/src/modules/citizen/components/CitizenInput.tsx
+++ b/app/src/modules/citizen/components/CitizenInput.tsx
@@ -3,7 +3,7 @@
import Button from "@/modules/common/components/Button";
import { CitizenLink } from "@/modules/common/components/CitizenLink";
import { underlineCharacters } from "@/modules/common/utils/underlineCharacters";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { api } from "@/trpc/react";
import {
Combobox,
@@ -283,8 +283,8 @@ const Multiple = ({
onClick={() => handleSelectRole(role.id)}
className="group"
>
-
diff --git a/app/src/modules/citizen/components/NotesTable.tsx b/app/src/modules/citizen/components/NotesTable.tsx
index a4fb924c0..a1ff897d8 100644
--- a/app/src/modules/citizen/components/NotesTable.tsx
+++ b/app/src/modules/citizen/components/NotesTable.tsx
@@ -126,7 +126,7 @@ export const NotesTable = ({ rows, searchParams }: Props) => {
{row.entityLog.content}
diff --git a/app/src/modules/citizen/components/OtherTable.tsx b/app/src/modules/citizen/components/OtherTable.tsx
index 95d2ae265..5e3ae314e 100644
--- a/app/src/modules/citizen/components/OtherTable.tsx
+++ b/app/src/modules/citizen/components/OtherTable.tsx
@@ -120,14 +120,14 @@ export const OtherTable = ({ rows, searchParams }: Props) => {
{entityLogTypeTranslations[row.entityLog.type]}
{row.entityLog.content}
diff --git a/app/src/modules/citizen/components/ProfileTile.tsx b/app/src/modules/citizen/components/ProfileTile.tsx
index 3d9037e41..47bc57769 100644
--- a/app/src/modules/citizen/components/ProfileTile.tsx
+++ b/app/src/modules/citizen/components/ProfileTile.tsx
@@ -2,7 +2,7 @@ import { requireAuthentication } from "@/modules/auth/server";
import Avatar from "@/modules/common/components/Avatar";
import { Link } from "@/modules/common/components/Link";
import { getPenaltyEntriesOfCurrentUser } from "@/modules/penalty-points/queries";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { getMyAssignedRoles } from "@/modules/roles/utils/getRoles";
import {
getMonthlySalaryOfCurrentCitizen,
@@ -61,7 +61,11 @@ export const ProfileTile = async ({ className }: Props) => {
{roles.length > 0 && (
{roles.map((role) => (
-
+
))}
)}
diff --git a/app/src/modules/citizen/components/RolesCell.tsx b/app/src/modules/citizen/components/RolesCell.tsx
index 53771b46d..b83b353e5 100644
--- a/app/src/modules/citizen/components/RolesCell.tsx
+++ b/app/src/modules/citizen/components/RolesCell.tsx
@@ -1,6 +1,6 @@
import { requireAuthentication } from "@/modules/auth/server";
import { AddRoles } from "@/modules/citizen/components/roles/AddRoles";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { getAssignedRoles } from "@/modules/roles/utils/getRoles";
import {
type Entity,
@@ -48,7 +48,11 @@ export const RolesCell = async ({
{roles.length > 0 ? (
{roles.map((role) => (
-
+
))}
) : (
diff --git a/app/src/modules/citizen/components/roles/Roles.tsx b/app/src/modules/citizen/components/roles/Roles.tsx
index 394cd5e70..b862d45e4 100644
--- a/app/src/modules/citizen/components/roles/Roles.tsx
+++ b/app/src/modules/citizen/components/roles/Roles.tsx
@@ -1,6 +1,6 @@
import { requireAuthentication } from "@/modules/auth/server";
import { Tile } from "@/modules/common/components/Tile";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import {
getAssignableRoles,
getAssignedRoles,
@@ -53,7 +53,11 @@ export const Roles = async ({ className, entity }: Props) => {
{assignedAndVisibleRoles.length > 0 ? (
{assignedAndVisibleRoles.map((role) => (
-
+
))}
) : (
diff --git a/app/src/modules/citizen/components/roles/RolesHistory.tsx b/app/src/modules/citizen/components/roles/RolesHistory.tsx
index 6d5aab379..49acd5955 100644
--- a/app/src/modules/citizen/components/roles/RolesHistory.tsx
+++ b/app/src/modules/citizen/components/roles/RolesHistory.tsx
@@ -3,7 +3,7 @@ import { requireAuthentication } from "@/modules/auth/server";
import { CitizenLink } from "@/modules/common/components/CitizenLink";
import { Tile } from "@/modules/common/components/Tile";
import { formatDate } from "@/modules/common/utils/formatDate";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { getVisibleRoles } from "@/modules/roles/utils/getRoles";
import { RoleAssignmentChangeType, type Entity } from "@prisma/client";
import clsx from "clsx";
@@ -46,22 +46,23 @@ export const RolesHistory = async ({ className, entity }: Props) => {
visibleRoles.some((visibleRole) => visibleRole.id === change.roleId),
)
.map((change) => {
- const role = visibleRoles.find((role) => role.id === change.roleId)!;
-
let message;
switch (change.type) {
case RoleAssignmentChangeType.ADD:
message = (
<>
- Die Rolle wurde
- hinzugefügt.
+ Die Rolle{" "}
+ {" "}
+ wurde hinzugefügt.
>
);
break;
case RoleAssignmentChangeType.REMOVE:
message = (
<>
- Die Rolle wurde entfernt.
+ Die Rolle{" "}
+ {" "}
+ wurde entfernt.
>
);
break;
diff --git a/app/src/modules/common/components/Badge.tsx b/app/src/modules/common/components/Badge.tsx
index 9a135cd26..6cf9b2786 100644
--- a/app/src/modules/common/components/Badge.tsx
+++ b/app/src/modules/common/components/Badge.tsx
@@ -32,19 +32,14 @@ export const Badge = ({
{label}
-
- {valueNode || value}
-
+ {valueNode || value}
{cta && {cta} }
diff --git a/app/src/modules/common/components/BooleanFilter.tsx b/app/src/modules/common/components/BooleanFilter.tsx
index 00962fa7c..82d8df805 100644
--- a/app/src/modules/common/components/BooleanFilter.tsx
+++ b/app/src/modules/common/components/BooleanFilter.tsx
@@ -45,9 +45,7 @@ export const BooleanFilter = ({ className, identifier, children }: Props) => {
className="flex gap-2 items-center cursor-pointer overflow-hidden"
htmlFor={id}
>
-
- {children}
-
+ {children}
);
diff --git a/app/src/modules/common/components/Popover.tsx b/app/src/modules/common/components/Popover.tsx
index 3b7eca725..5bbfe1de1 100644
--- a/app/src/modules/common/components/Popover.tsx
+++ b/app/src/modules/common/components/Popover.tsx
@@ -64,9 +64,10 @@ export const Popover = ({
{enableHover && (
-
+ <>
+
+
+ >
)}
diff --git a/app/src/modules/events/components/ParticipantsTab.tsx b/app/src/modules/events/components/ParticipantsTab.tsx
index ff9ceaa72..464037abe 100644
--- a/app/src/modules/events/components/ParticipantsTab.tsx
+++ b/app/src/modules/events/components/ParticipantsTab.tsx
@@ -218,10 +218,7 @@ export const ParticipantsTab = async ({
-
+
Rollen/Zertifikate
diff --git a/app/src/modules/events/components/Position.tsx b/app/src/modules/events/components/Position.tsx
index 9ea932a31..f2fbae649 100644
--- a/app/src/modules/events/components/Position.tsx
+++ b/app/src/modules/events/components/Position.tsx
@@ -152,9 +152,7 @@ export const Position = ({
className="font-bold"
/>
) : (
-
- {position.name}
-
+
{position.name}
)}
diff --git a/app/src/modules/events/components/RolesTable.tsx b/app/src/modules/events/components/RolesTable.tsx
index e76b4e152..5aad15ef5 100644
--- a/app/src/modules/events/components/RolesTable.tsx
+++ b/app/src/modules/events/components/RolesTable.tsx
@@ -1,6 +1,6 @@
"use client";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import type { Role, Upload } from "@prisma/client";
import {
createColumnHelper,
@@ -43,7 +43,7 @@ export const RolesTable = ({ className, rows }: Props) => {
id: "name",
cell: (row) => {
const { role } = row.row.original;
- return ;
+ return ;
},
}),
columnHelper.accessor("count", {
diff --git a/app/src/modules/fleet/components/ManufacturersTile.tsx b/app/src/modules/fleet/components/ManufacturersTile.tsx
index 5f9e1e69a..d72a1a475 100644
--- a/app/src/modules/fleet/components/ManufacturersTile.tsx
+++ b/app/src/modules/fleet/components/ManufacturersTile.tsx
@@ -60,10 +60,7 @@ export const ManufacturersTile = async () => {
)}
-
+
{
GRID_COLS,
)}
>
-
+
)}
-
diff --git a/app/src/modules/fleet/components/VariantsTile.tsx b/app/src/modules/fleet/components/VariantsTile.tsx
index bd1fac763..24b304779 100644
--- a/app/src/modules/fleet/components/VariantsTile.tsx
+++ b/app/src/modules/fleet/components/VariantsTile.tsx
@@ -63,10 +63,7 @@ export const VariantsTile = async ({
GRID_COLS,
)}
>
-
+
{row.name}
@@ -79,7 +76,7 @@ export const VariantsTile = async ({
) => {
return (
-
+
{unit.title}-{positions[type]}
{member.user.name}
@@ -67,7 +64,7 @@ const SquadronFlightPosition = ({
{member.ship.variant.name}
diff --git a/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx b/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx
index 53d6d4a8c..716fc084d 100644
--- a/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx
+++ b/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx
@@ -114,17 +114,11 @@ const SquadronFlightPositionEmpty = ({ type, unit }: Readonly) => {
onClick={() => setIsOpen(true)}
title="Position besetzen"
>
-
+
{unit.title}-{positions[type]}
-
+
Unbesetzt
diff --git a/app/src/modules/roles/components/DeleteRole.tsx b/app/src/modules/roles/components/DeleteRole.tsx
index 3cf359de6..02814a818 100644
--- a/app/src/modules/roles/components/DeleteRole.tsx
+++ b/app/src/modules/roles/components/DeleteRole.tsx
@@ -36,7 +36,7 @@ export const DeleteRole = ({ className, role }: Props) => {
- {isPending ? : }{" "}
+ {isPending ? : }
Löschen
diff --git a/app/src/modules/roles/components/InheritanceForm.tsx b/app/src/modules/roles/components/InheritanceForm.tsx
index 846604b55..348c94e3d 100644
--- a/app/src/modules/roles/components/InheritanceForm.tsx
+++ b/app/src/modules/roles/components/InheritanceForm.tsx
@@ -2,7 +2,7 @@
import { Button2 } from "@/modules/common/components/Button2";
import Note from "@/modules/common/components/Note";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import type { Role, Upload } from "@prisma/client";
import clsx from "clsx";
import { useActionState } from "react";
@@ -53,8 +53,8 @@ export const InheritanceForm = ({ className, currentRole, roles }: Props) => {
-
diff --git a/app/src/modules/roles/components/PermissionMatrix.tsx b/app/src/modules/roles/components/PermissionMatrix.tsx
index afea29d3c..90c31091a 100644
--- a/app/src/modules/roles/components/PermissionMatrix.tsx
+++ b/app/src/modules/roles/components/PermissionMatrix.tsx
@@ -639,9 +639,7 @@ const Row = ({ role, permissions, gridTemplateColumns }: RowProps) => {
)}
-
- {role.name}
-
+ {role.name}
diff --git a/app/src/modules/roles/components/RolesContext.tsx b/app/src/modules/roles/components/RolesContext.tsx
new file mode 100644
index 000000000..251431bae
--- /dev/null
+++ b/app/src/modules/roles/components/RolesContext.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import type { Role, Upload } from "@prisma/client";
+import type { ReactNode } from "react";
+import { createContext, useContext, useMemo } from "react";
+
+interface RolesContext {
+ readonly roles: (Role & {
+ icon: Upload | null;
+ thumbnail: Upload | null;
+ })[];
+}
+
+const RolesContext = createContext(undefined);
+
+interface Props {
+ readonly children: ReactNode;
+ readonly roles: (Role & {
+ icon: Upload | null;
+ thumbnail: Upload | null;
+ })[];
+}
+
+export const RolesContextProvider = ({ children, roles }: Props) => {
+ const value = useMemo(
+ () => ({
+ roles,
+ }),
+ [roles],
+ );
+
+ return (
+ {children}
+ );
+};
+
+/**
+ * Check for undefined since the defaultValue of the context is undefined. If
+ * it's still undefined, the provider component is missing.
+ */
+export function useRolesContext() {
+ const context = useContext(RolesContext);
+ if (!context) throw new Error("Provider is missing.");
+ return context;
+}
diff --git a/app/src/modules/roles/components/SingleRole.tsx b/app/src/modules/roles/components/SingleRole.tsx
deleted file mode 100644
index 08d14ff3f..000000000
--- a/app/src/modules/roles/components/SingleRole.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { env } from "@/env";
-import type { Upload } from "@prisma/client";
-import { type Role } from "@prisma/client";
-import clsx from "clsx";
-import Image from "next/image";
-
-interface Props {
- readonly className?: string;
- readonly role: Role & {
- icon: Upload | null;
- };
- readonly showPlaceholder?: boolean;
-}
-
-export const SingleRole = ({
- className,
- role,
- showPlaceholder = false,
-}: Props) => {
- return (
-
- {role.icon && (
-
-
-
- )}
-
- {!role.iconId && showPlaceholder && }
-
-
- {role.name}
-
-
- );
-};
diff --git a/app/src/modules/roles/components/SingleRoleBadge.tsx b/app/src/modules/roles/components/SingleRoleBadge.tsx
new file mode 100644
index 000000000..8021a1531
--- /dev/null
+++ b/app/src/modules/roles/components/SingleRoleBadge.tsx
@@ -0,0 +1,169 @@
+"use client";
+
+import { env } from "@/env";
+import { useAction } from "@/modules/actions/utils/useAction";
+import { useAuthentication } from "@/modules/auth/hooks/useAuthentication";
+import { deleteRoleAssignment } from "@/modules/citizen/actions/deleteRoleAssignment";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/modules/common/components/AlertDialog";
+import { Button2, Button2Variant } from "@/modules/common/components/Button2";
+import { Note } from "@/modules/common/components/Note";
+import { Popover } from "@/modules/common/components/Popover";
+import { type Role } from "@prisma/client";
+import clsx from "clsx";
+import Image from "next/image";
+import { useId } from "react";
+import { FaSpinner, FaTrash } from "react-icons/fa";
+import { useRolesContext } from "./RolesContext";
+
+interface Props {
+ readonly className?: string;
+ readonly roleId: Role["id"];
+ readonly showPlaceholder?: boolean;
+ readonly citizenId?: string;
+}
+
+export const SingleRoleBadge = ({
+ className,
+ roleId,
+ showPlaceholder = false,
+ citizenId,
+}: Props) => {
+ const { roles } = useRolesContext();
+ const authentication = useAuthentication();
+ const { state, formAction, isPending } = useAction(deleteRoleAssignment);
+ const formId = useId();
+
+ const role = roles.find((role) => role.id === roleId);
+ if (!role) return null;
+
+ const canDismiss =
+ authentication &&
+ authentication.authorize("otherRole", "dismiss", [
+ {
+ key: "roleId",
+ value: role.id,
+ },
+ ]);
+
+ return (
+
+ {role.icon && (
+
+
+
+ )}
+
+ {!role.iconId && showPlaceholder && }
+
+ {role.name}
+
+ }
+ >
+
+
+ {role.icon ? (
+
+
+
+ ) : (
+
+ )}
+
+ {role.name}
+
+
+ {citizenId && canDismiss && (
+
+ )}
+
+
+ );
+};
diff --git a/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx b/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx
index 8e90ea591..b3e1e5cb5 100644
--- a/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx
+++ b/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx
@@ -1,6 +1,6 @@
import Button from "@/modules/common/components/Button";
import { api } from "@/modules/common/utils/api";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import type { Role } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import { useState, type CSSProperties } from "react";
@@ -45,10 +45,10 @@ export const RoleSelector = ({ style, defaultValue, onChange }: Props) => {
className="flex items-center justify-between gap-1 bg-neutral-700/50 hover:bg-neutral-600/50 pr-3 rounded-secondary"
style={style}
>
- role.role.id === selectedRole)!.role
+ roleId={
+ data.find((role) => role.role.id === selectedRole)!.role.id
}
showPlaceholder
/>
@@ -91,8 +91,8 @@ export const RoleSelector = ({ style, defaultValue, onChange }: Props) => {
onClick={() => handleSelectRole(role.role.id)}
className="group"
>
-
diff --git a/app/src/modules/silc/components/SilcTransactionsTableClient.tsx b/app/src/modules/silc/components/SilcTransactionsTableClient.tsx
index 8293f350d..2fc981918 100644
--- a/app/src/modules/silc/components/SilcTransactionsTableClient.tsx
+++ b/app/src/modules/silc/components/SilcTransactionsTableClient.tsx
@@ -66,7 +66,7 @@ export const SilcTransactionsTableClient = ({
return (
@@ -97,10 +97,7 @@ export const SilcTransactionsTableClient = ({
id: "description",
enableSorting: false,
cell: (row) => (
-
+
{row.getValue()}
),
@@ -117,7 +114,7 @@ export const SilcTransactionsTableClient = ({
return (
diff --git a/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx b/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx
index d22e5d49e..9e4188dd1 100644
--- a/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx
+++ b/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx
@@ -1,6 +1,6 @@
import { requireAuthentication } from "@/modules/auth/server";
import { CitizenLink } from "@/modules/common/components/CitizenLink";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import { getVisibleRoles } from "@/modules/roles/utils/getRoles";
import {
RoleAssignmentChangeType,
@@ -27,15 +27,14 @@ export const mapRoleAssignmentChangeEntries = async (
visibleRoles.some((visibleRole) => visibleRole.id === change.roleId),
)
.map((change) => {
- const role = visibleRoles.find((role) => role.id === change.roleId)!;
-
let message;
switch (change.type) {
case RoleAssignmentChangeType.ADD:
message = (
Citizen wurde die Rolle{" "}
- hinzugefügt
+ {" "}
+ hinzugefügt
);
break;
@@ -43,7 +42,8 @@ export const mapRoleAssignmentChangeEntries = async (
message = (
Citizen wurde die Rolle{" "}
- entfernt
+ {" "}
+ entfernt
);
break;
diff --git a/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx b/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx
index e92877358..6d99da307 100644
--- a/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx
+++ b/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx
@@ -1,6 +1,6 @@
import { Button2, Button2Variant } from "@/modules/common/components/Button2";
import { api } from "@/modules/common/utils/api";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import type { Role } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import clsx from "clsx";
@@ -63,8 +63,8 @@ export const RequiredRoles = ({ className, defaultValue }: Props) => {
onClick={() => handleSelectRole(role.id)}
className="group"
>
-
@@ -103,9 +103,9 @@ export const RequiredRoles = ({ className, defaultValue }: Props) => {
}
className="flex items-center gap-1 bg-neutral-700/50 pr-2 rounded-secondary"
>
-
diff --git a/app/src/modules/tasks/components/Overview.tsx b/app/src/modules/tasks/components/Overview.tsx
index 19399e27b..8b1d6cca5 100644
--- a/app/src/modules/tasks/components/Overview.tsx
+++ b/app/src/modules/tasks/components/Overview.tsx
@@ -10,7 +10,7 @@ import { Markdown } from "@/modules/common/components/Markdown";
import { Tile } from "@/modules/common/components/Tile";
import { Tooltip } from "@/modules/common/components/Tooltip";
import { formatDate } from "@/modules/common/utils/formatDate";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import {
TaskRewardType,
TaskVisibility,
@@ -164,7 +164,11 @@ export const Overview = ({
{task.requiredRoles.length > 0 ? (
{task.requiredRoles.map((role) => (
-
+
))}
) : (
diff --git a/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx b/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx
index ed867fe4b..4a43cfb47 100644
--- a/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx
+++ b/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx
@@ -2,7 +2,7 @@
import { useAuthentication } from "@/modules/auth/hooks/useAuthentication";
import { Button2 } from "@/modules/common/components/Button2";
-import { SingleRole } from "@/modules/roles/components/SingleRole";
+import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge";
import {
TaskVisibility,
type Role,
@@ -148,7 +148,7 @@ export const ToggleAssignmentForCurrentUser = ({
{task.requiredRoles.map((role) => (
-
+
))}