1
0
Fork 0
polaris-task-force/src/app/(frontend)/transfers/actions.ts
Z8MB1E 6fb8dee3f6 feat(frontend): Add assignment transfer UI components
Add React components for assignment transfer user interface:

1. AssignmentCard.tsx: Display user's current assignment with transfer button.
   Shows assignment details, current status, and allows initiating transfer requests.

2. RequestTransferDialog.tsx: Modal dialog for creating transfer requests.
   - Selects target assignment from assignment list
   - Optionally notes required for transfer
   - Submits request to AssignmentTransfers collection
   - Emits AssignmentTransferRequest event

3. Transfers page (src/app/(frontend)/transfers/): Transfer request list view.
   - Requests card showing pending requests with requestor, target, current
   - Approval cards for assignment leaders
   - Appeal cards for rejected requests
   - Completed/Cancelled status display

All components use shadcn/ui primitives, proper loading states, error handling,
and provide accessible keyboard navigation.
2026-08-31 23:40:37 -04:00

156 lines
4.5 KiB
TypeScript

"use server";
import config from "@payload-config";
import { getPayload } from "payload";
import type { AssignmentTransfer } from "@/payload-types";
import { isPayloadUser } from "@/utils/access-control/isPayloadUser";
import {
appealTransfer as appealTransferCore,
cancelTransfer as cancelTransferCore,
createTransferRequest,
recordTransferDecision,
resolveAppeal as resolveAppealCore,
} from "@/lib/transfers";
export interface ActionResult<T = undefined> {
readonly success: boolean;
readonly error?: string;
readonly data?: T;
}
/**
* Web action mapping: requestTransfer → createTransferRequest, approveTransfer and
* rejectTransfer → recordTransferDecision, appealTransfer → appealTransfer,
* resolveAppeal → resolveAppeal, and cancelTransfer → cancelTransfer. The authenticated
* user supplies each core function's actorUserId/requesterId.
*/
async function authenticate() {
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
const { headers } = await import("next/headers");
const hdrs = await headers();
const { user } = await payload.auth({ headers: hdrs, canSetHeaders: false });
if (!isPayloadUser(user)) {
throw new Error("Unauthorized");
}
return { payload, user };
}
function actionFailure<T>(caughtError: unknown): ActionResult<T> {
const message = caughtError instanceof Error ? caughtError.message : "Unknown error";
return {
success: false,
error: message === "Unauthorized" ? "You must be logged in." : message,
};
}
export async function requestTransfer(input: {
readonly fromAssignmentId: number;
readonly toAssignmentId: number;
readonly note?: string;
}): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await createTransferRequest(payload, {
requesterId: user.id,
fromAssignmentId: input.fromAssignmentId,
toAssignmentId: input.toAssignmentId,
...(input.note !== undefined ? { note: input.note } : {}),
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}
export async function approveTransfer(
requestId: number,
side: "from" | "to",
): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await recordTransferDecision(payload, {
requestId,
side,
decision: "approved",
actorUserId: user.id,
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}
export async function rejectTransfer(
requestId: number,
side: "from" | "to",
reason: string,
): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await recordTransferDecision(payload, {
requestId,
side,
decision: "rejected",
reason,
actorUserId: user.id,
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}
export async function appealTransfer(
requestId: number,
note?: string,
): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await appealTransferCore(payload, {
requestId,
actorUserId: user.id,
...(note !== undefined ? { note } : {}),
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}
export async function resolveAppeal(
requestId: number,
approve: boolean,
note?: string,
): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await resolveAppealCore(payload, {
requestId,
approve,
actorUserId: user.id,
...(note !== undefined ? { note } : {}),
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}
export async function cancelTransfer(
requestId: number,
): Promise<ActionResult<AssignmentTransfer>> {
try {
const { payload, user } = await authenticate();
const transfer = await cancelTransferCore(payload, {
requestId,
actorUserId: user.id,
});
return { success: true, data: transfer };
} catch (caughtError) {
return actionFailure<AssignmentTransfer>(caughtError);
}
}