support delete org and preserve path on switch

This commit is contained in:
miloschwartz
2026-02-17 16:45:15 -08:00
parent b71f582329
commit 79cf7c84dc
4 changed files with 61 additions and 18 deletions

View File

@@ -86,16 +86,14 @@ authenticated.post(
org.updateOrg
);
if (build !== "saas") {
authenticated.delete(
"/org/:orgId",
verifyOrgAccess,
verifyUserIsOrgOwner,
verifyUserHasAction(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg),
org.deleteOrg
);
}
authenticated.delete(
"/org/:orgId",
verifyOrgAccess,
verifyUserIsOrgOwner,
verifyUserHasAction(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg),
org.deleteOrg
);
authenticated.put(
"/org/:orgId/site",

View File

@@ -7,6 +7,8 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
import { db, userOrgs, orgs } from "@server/db";
import { eq, and } from "drizzle-orm";
const deleteOrgSchema = z.strictObject({
orgId: z.string()
@@ -41,6 +43,48 @@ export async function deleteOrg(
);
}
const { orgId } = parsedParams.data;
const [data] = await db
.select()
.from(userOrgs)
.innerJoin(orgs, eq(userOrgs.orgId, orgs.orgId))
.where(
and(
eq(userOrgs.orgId, orgId),
eq(userOrgs.userId, req.user!.userId)
)
);
const org = data?.orgs;
const userOrg = data?.userOrgs;
if (!org || !userOrg) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
if (!userOrg.isOwner) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Only organization owners can delete the organization"
)
);
}
if (org.isBillingOrg) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Cannot delete a primary organization"
)
);
}
const result = await deleteOrgById(orgId);
sendTerminationMessages(result);
return response(res, {

View File

@@ -3,11 +3,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { Button } from "@app/components/ui/button";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { toast } from "@app/hooks/useToast";
import {
useState,
useTransition,
useActionState
} from "react";
import { useState, useTransition, useActionState } from "react";
import {
Form,
FormControl,
@@ -54,7 +50,7 @@ export default function GeneralPage() {
return (
<SettingsContainer>
<GeneralSectionForm org={org.org} />
{build !== "saas" && <DeleteForm org={org.org} />}
{!org.org.isBillingOrg && <DeleteForm org={org.org} />}
</SettingsContainer>
);
}

View File

@@ -25,7 +25,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { cn } from "@app/lib/cn";
import { ListUserOrgsResponse } from "@server/routers/org";
import { Check, ChevronsUpDown, Plus, Building2, Users } from "lucide-react";
import { useRouter } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import { useMemo, useState } from "react";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
@@ -44,6 +44,7 @@ export function OrgSelector({
const { user } = useUserContext();
const [open, setOpen] = useState(false);
const router = useRouter();
const pathname = usePathname();
const { env } = useEnvContext();
const t = useTranslations();
@@ -141,7 +142,11 @@ export function OrgSelector({
key={org.orgId}
onSelect={() => {
setOpen(false);
router.push(`/${org.orgId}/settings`);
const newPath = pathname.replace(
/^\/[^/]+/,
`/${org.orgId}`
);
router.push(newPath);
}}
className="mx-2 rounded-md"
>