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 org.updateOrg
); );
if (build !== "saas") { authenticated.delete(
authenticated.delete( "/org/:orgId",
"/org/:orgId", verifyOrgAccess,
verifyOrgAccess, verifyUserIsOrgOwner,
verifyUserIsOrgOwner, verifyUserHasAction(ActionsEnum.deleteOrg),
verifyUserHasAction(ActionsEnum.deleteOrg), logActionAudit(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg), org.deleteOrg
org.deleteOrg );
);
}
authenticated.put( authenticated.put(
"/org/:orgId/site", "/org/:orgId/site",

View File

@@ -7,6 +7,8 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg"; import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
import { db, userOrgs, orgs } from "@server/db";
import { eq, and } from "drizzle-orm";
const deleteOrgSchema = z.strictObject({ const deleteOrgSchema = z.strictObject({
orgId: z.string() orgId: z.string()
@@ -41,6 +43,48 @@ export async function deleteOrg(
); );
} }
const { orgId } = parsedParams.data; 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); const result = await deleteOrgById(orgId);
sendTerminationMessages(result); sendTerminationMessages(result);
return response(res, { return response(res, {

View File

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

View File

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