88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs } from "react-router";
|
|
import { loadRequiredWebConfig } from "@hivegrid/config";
|
|
import { buildSelectTenantVM, type SelectTenantData } from "@hivegrid/view-models";
|
|
import type { VMState } from "@hivegrid/view-models";
|
|
import { getTranslations } from "~/locale/i18next.server";
|
|
import { absoluteAppPath } from "~/utils/app/CrossHostUrls";
|
|
import { adminGetAllActiveTenantSimple } from "~/utils/db/tenants.db.server";
|
|
import { verifyUserHasPermission } from "~/utils/helpers/.server/PermissionsService";
|
|
import { getUserInfo, validateCSRFToken } from "~/utils/session.server";
|
|
|
|
type AdminSelectTenantVM = VMState<SelectTenantData>;
|
|
|
|
export type AdminSelectTenantLoaderData = {
|
|
title: string;
|
|
vm: AdminSelectTenantVM;
|
|
copy: {
|
|
titleKey: string;
|
|
subtitleKey: string;
|
|
infoTooltipKey: string;
|
|
searchPlaceholderKey: string;
|
|
emptyKey: string;
|
|
noResultsKey: string;
|
|
sectionLabelKey: string;
|
|
};
|
|
showCreateAction: false;
|
|
};
|
|
|
|
export type AdminSelectTenantActionData = {
|
|
error?: string;
|
|
};
|
|
|
|
const copy: AdminSelectTenantLoaderData["copy"] = {
|
|
titleKey: "admin.selectTenant.title",
|
|
subtitleKey: "admin.selectTenant.subtitle",
|
|
infoTooltipKey: "admin.selectTenant.infoTooltip",
|
|
searchPlaceholderKey: "admin.selectTenant.searchPlaceholder",
|
|
emptyKey: "admin.selectTenant.empty",
|
|
noResultsKey: "admin.selectTenant.noResults",
|
|
sectionLabelKey: "admin.selectTenant.sectionLabel",
|
|
};
|
|
|
|
async function requireAdminTenantSwitchAccess(request: Request) {
|
|
const userInfo = await getUserInfo(request);
|
|
if (!userInfo.userId) {
|
|
throw redirect("/login");
|
|
}
|
|
await verifyUserHasPermission(request, "admin.accounts.view");
|
|
return userInfo;
|
|
}
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
await requireAdminTenantSwitchAccess(request);
|
|
const { t } = await getTranslations(request);
|
|
const tenants = await adminGetAllActiveTenantSimple();
|
|
const vm = buildSelectTenantVM({
|
|
tenants: tenants.map((tenant) => ({
|
|
id: tenant.id,
|
|
name: tenant.name,
|
|
slug: tenant.slug,
|
|
image: tenant.icon ?? tenant.logo ?? null,
|
|
role: null,
|
|
subtitle: null,
|
|
})),
|
|
});
|
|
return Response.json({
|
|
title: t("admin.selectTenant.title"),
|
|
vm,
|
|
copy,
|
|
showCreateAction: false,
|
|
} satisfies AdminSelectTenantLoaderData);
|
|
}
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
await requireAdminTenantSwitchAccess(request);
|
|
const form = await request.formData();
|
|
await validateCSRFToken(request, { formData: form });
|
|
const tenantId = String(form.get("tenantId") ?? "");
|
|
const slug = String(form.get("slug") ?? "");
|
|
if (!tenantId || !slug) {
|
|
return Response.json({ error: "admin.selectTenant.errors.invalidSelection" } satisfies AdminSelectTenantActionData, { status: 400 });
|
|
}
|
|
const tenant = (await adminGetAllActiveTenantSimple()).find((candidate) => candidate.id === tenantId);
|
|
if (!tenant || tenant.slug !== slug) {
|
|
return Response.json({ error: "admin.selectTenant.errors.invalidSelection" } satisfies AdminSelectTenantActionData, { status: 400 });
|
|
}
|
|
throw redirect(absoluteAppPath(loadRequiredWebConfig().routing, tenant.slug, "dashboard"));
|
|
}
|