mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on the Organization side.
This commit is contained in:
+16
-4
@@ -5,6 +5,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { setCurrentOrganizationSlug } from "@/features/dashboard/organization-cookie";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import {authClient} from "@/lib/auth/auth-client";
|
||||
|
||||
export type DeleteOrganizationButtonProps = {
|
||||
organizationSlug: string;
|
||||
@@ -14,15 +15,26 @@ export const DeleteOrganizationButton = (props: DeleteOrganizationButtonProps) =
|
||||
const router = useRouter();
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => deleteOrganizationAction(props.organizationSlug),
|
||||
|
||||
onSuccess: async (result) => {
|
||||
if (result.data.success) {
|
||||
await setCurrentOrganizationSlug("default");
|
||||
if (result?.data?.success) {
|
||||
await authClient.organization.setActive({
|
||||
organizationSlug: "default",
|
||||
});
|
||||
router.push("/");
|
||||
toast.success(result.data.actionSuccess.message);
|
||||
|
||||
toast.success(result.data.actionSuccess?.message || "Organization deleted.");
|
||||
} else {
|
||||
toast.error(result.data.actionError.message);
|
||||
// @ts-ignore
|
||||
const errorMsg = result?.data?.actionError?.message || result?.data?.actionError?.messageParams?.message || "Failed to delete the organization.";
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
onError: (error: any) => {
|
||||
console.error("Mutation network error:", error);
|
||||
toast.error(error?.message || "A network error occurred.");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
+16
-13
@@ -9,34 +9,35 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
|
||||
import { OrganizationFormSchema, OrganizationFormType } from "@/components/wrappers/dashboard/organization/OrganizationForm/organization-form.schema";
|
||||
import { updateOrganizationAction } from "@/components/wrappers/dashboard/organization/organization.action";
|
||||
import { toast } from "sonner";
|
||||
import {Member, Organization} from "better-auth/plugins";
|
||||
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/02_organization";
|
||||
|
||||
export type organizationFormProps = {
|
||||
defaultValues?: Organization;
|
||||
members: Member[];
|
||||
defaultValues?: OrganizationWithMembers;
|
||||
members: MemberWithUser[];
|
||||
};
|
||||
|
||||
export const OrganizationForm = (props: organizationFormProps) => {
|
||||
|
||||
const router = useRouter();
|
||||
const isCreate = !Boolean(props.defaultValues);
|
||||
|
||||
const formatUsersList = (members: Member[]) => {
|
||||
const formatUsersList = (members: MemberWithUser[]) => {
|
||||
|
||||
return members.map((member) => ({
|
||||
value: member.id,
|
||||
value: member.user.id,
|
||||
label: `${member.user.name} | ${member.user.email}`,
|
||||
}));
|
||||
};
|
||||
|
||||
const formatDefaultUsers = (members: OrganizationFormType["members"]): string[] => {
|
||||
console.log(members);
|
||||
const formatDefaultUsers = (members: MemberWithUser[]): string[] => {
|
||||
console.log("ici",members);
|
||||
return members.map((member) => member.userId);
|
||||
};
|
||||
|
||||
const formattedDefaultValues = {
|
||||
...props.defaultValues,
|
||||
users: !isCreate ? formatDefaultUsers(props.defaultValues?.members) : [],
|
||||
name: props.defaultValues?.name,
|
||||
slug: props.defaultValues?.slug,
|
||||
users: !isCreate ? formatDefaultUsers(props.defaultValues?.members as MemberWithUser[]) : [],
|
||||
};
|
||||
|
||||
const form = useZodForm({
|
||||
@@ -59,6 +60,8 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
console.log(formatUsersList(props.members))
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader></CardHeader>
|
||||
@@ -110,13 +113,13 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
||||
name="users"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Databases</FormLabel>
|
||||
<FormLabel>Users</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={formatUsersList(props.members)}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value ?? []}
|
||||
placeholder="Select databases"
|
||||
placeholder="Select users"
|
||||
variant="inverted"
|
||||
animation={2}
|
||||
// maxCount={100}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {createOrganizationAction} from "@/components/wrappers/dashboard/organiza
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useState} from "react";
|
||||
import {authClient} from "@/lib/auth/auth-client";
|
||||
import {toast} from "sonner";
|
||||
|
||||
export type createOrganizationModalProps = {
|
||||
open: boolean;
|
||||
@@ -35,15 +36,19 @@ export function CreateOrganizationModal({open, onOpenChange, onSuccess}: createO
|
||||
|
||||
const result = await createOrganizationAction(values);
|
||||
|
||||
if (result && result.data) {
|
||||
if (result.data.success && result.data.value) {
|
||||
onOpenChange(false);
|
||||
await authClient.organization.setActive({organizationSlug: result.data.value.slug});
|
||||
onSuccess?.();
|
||||
router.replace(`/dashboard/home`);
|
||||
// router.refresh();
|
||||
}
|
||||
if (result?.data?.success && result.data.value) {
|
||||
onOpenChange(false);
|
||||
await authClient.organization.setActive({organizationSlug: result.data.value.slug});
|
||||
onSuccess?.();
|
||||
toast.success(result.data.actionSuccess?.message || "Organization Created.");
|
||||
// router.push("/");
|
||||
router.replace(`/dashboard/home`);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const errorMsg = result?.data?.actionError?.message || result?.data?.actionError?.messageParams?.message || "Failed to create the organization.";
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
@@ -77,26 +82,26 @@ export function CreateOrganizationModal({open, onOpenChange, onSuccess}: createO
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="slug"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Slug</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replaceAll(" ", "-").toLowerCase();
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/*<FormField*/}
|
||||
{/* control={form.control}*/}
|
||||
{/* name="slug"*/}
|
||||
{/* defaultValue=""*/}
|
||||
{/* render={({field}) => (*/}
|
||||
{/* <FormItem>*/}
|
||||
{/* <FormLabel>Slug</FormLabel>*/}
|
||||
{/* <FormControl>*/}
|
||||
{/* <Input*/}
|
||||
{/* {...field}*/}
|
||||
{/* onChange={(e) => {*/}
|
||||
{/* const value = e.target.value.replaceAll(" ", "-").toLowerCase();*/}
|
||||
{/* field.onChange(value);*/}
|
||||
{/* }}*/}
|
||||
{/* />*/}
|
||||
{/* </FormControl>*/}
|
||||
{/* <FormMessage/>*/}
|
||||
{/* </FormItem>*/}
|
||||
{/* )}*/}
|
||||
{/*/>*/}
|
||||
<DialogFooter>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<Button type="submit">Create</Button>
|
||||
|
||||
@@ -7,13 +7,16 @@ import { z } from "zod";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { OrganizationFormSchema } from "@/components/wrappers/dashboard/organization/OrganizationForm/organization-form.schema";
|
||||
import { db } from "@/db";
|
||||
import { Organization, organization as drizzleOrganization, organizationMember as drizzleOrganizationMember } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { checkSlugOrganization, createOrganization } from "@/lib/auth/auth";
|
||||
import {checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth";
|
||||
import {slugify} from "@/utils/slugify";
|
||||
import {Organization} from "@/db/schema/02_organization";
|
||||
import * as drizzleDb from "@/db";
|
||||
|
||||
export const createOrganizationAction = userAction.schema(OrganizationSchema).action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
|
||||
try {
|
||||
if (!checkSlugOrganization(parsedInput.slug)) {
|
||||
const slug = slugify(parsedInput.name);
|
||||
if (!await checkSlugOrganization(slug)) {
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
@@ -24,14 +27,29 @@ export const createOrganizationAction = userAction.schema(OrganizationSchema).ac
|
||||
};
|
||||
}
|
||||
|
||||
const organization = await createOrganization(parsedInput.name, parsedInput.slug);
|
||||
let createdOrganization: Organization;
|
||||
|
||||
try {
|
||||
createdOrganization = await createOrganization(parsedInput.name, slug) as unknown as Organization;
|
||||
} catch (authError: any) {
|
||||
console.error("Auth deletion failed:", authError);
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: authError.message || "Authentication service error.",
|
||||
status: authError.status || 500,
|
||||
cause: "auth_error",
|
||||
messageParams: { message: authError.message },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: organization!,
|
||||
value: createdOrganization,
|
||||
actionSuccess: {
|
||||
message: "Organization has been successfully created.",
|
||||
messageParams: { organizationId: organization!.id },
|
||||
messageParams: { organizationId: createdOrganization!.id },
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -58,7 +76,7 @@ export const updateOrganizationAction = userAction
|
||||
try {
|
||||
const newUserList = parsedInput.data.users;
|
||||
|
||||
const organization = await db.select().from(organization).where(eq(organization.id, parsedInput.organizationId)).execute();
|
||||
const organization = await db.select().from(drizzleDb.schemas.organization).where(eq(drizzleDb.schemas.organization.id, parsedInput.organizationId)).execute();
|
||||
|
||||
if (organization.length === 0) {
|
||||
throw new Error("Organization not found.");
|
||||
@@ -70,7 +88,7 @@ export const updateOrganizationAction = userAction
|
||||
|
||||
if (usersToAdd.length > 0) {
|
||||
await db
|
||||
.insert(userOrganization)
|
||||
.insert(drizzleDb.schemas.organization)
|
||||
.values(
|
||||
usersToAdd.map((userId) => ({
|
||||
userId,
|
||||
@@ -117,44 +135,60 @@ export const updateOrganizationAction = userAction
|
||||
}
|
||||
});
|
||||
|
||||
export const deleteOrganizationAction = userAction.schema(z.string()).action(async ({ parsedInput, ctx }): Promise<ServerActionResult<Organization>> => {
|
||||
try {
|
||||
const uuid = uuidv4();
|
||||
const organization = await db.select().from(organization).where(eq(organization.slug, parsedInput)).execute();
|
||||
export const deleteOrganizationAction = userAction.schema(z.string()).action(
|
||||
async ({ parsedInput, ctx }): Promise<ServerActionResult<Organization>> => {
|
||||
try {
|
||||
const org = await db.query.organization.findFirst({
|
||||
where: eq(drizzleDb.schemas.organization.slug, parsedInput),
|
||||
});
|
||||
|
||||
if (organization.length === 0) {
|
||||
throw new Error("Organization not found.");
|
||||
if (!org) {
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: "Organization not found.",
|
||||
status: 404,
|
||||
cause: "not_found",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let deletedOrganization: Organization;
|
||||
|
||||
try {
|
||||
deletedOrganization = await deleteOrganization(org.id) as Organization;
|
||||
} catch (authError: any) {
|
||||
console.error("Auth deletion failed:", authError);
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: authError.message || "Authentication service error.",
|
||||
status: authError.status || 500,
|
||||
cause: "auth_error",
|
||||
messageParams: { message: authError.message },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: deletedOrganization,
|
||||
actionSuccess: {
|
||||
message: "Organization has been successfully deleted.",
|
||||
messageParams: { organizationId: deletedOrganization.id },
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Unexpected error in deleteOrganizationAction:", error);
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: "Failed to delete organization due to a server error.",
|
||||
status: 500,
|
||||
cause: "server_error",
|
||||
messageParams: { message: "Internal server error while deleting the organization" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const updatedOrganization = await db
|
||||
.update(organization)
|
||||
.set({
|
||||
name: `${organization[0].name}-${uuid}`,
|
||||
slug: `${organization[0].slug}-${uuid}`,
|
||||
deleted: true,
|
||||
})
|
||||
.where(eq(organization.id, organization[0].id))
|
||||
.returning()
|
||||
.execute();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: updatedOrganization[0],
|
||||
actionSuccess: {
|
||||
message: "Organization has been successfully deleted.",
|
||||
messageParams: { organizationId: updatedOrganization[0].id },
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error deleting organization:", error);
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: "Failed to delete organization.",
|
||||
status: 500,
|
||||
cause: error.message ?? "Unknown error",
|
||||
messageParams: { message: "Error deleting the organization" },
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
@@ -2,11 +2,11 @@ import { z } from "zod";
|
||||
|
||||
export const OrganizationSchema = z.object({
|
||||
name: z.string().min(5, "Name must be at least 5 characters long").max(40, "Name must be at most 40 characters long"),
|
||||
slug: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9_-]*$/, "Slug can only contain letters, numbers, underscores, and hyphens")
|
||||
.min(5, "Slug must be at least 5 characters long")
|
||||
.max(20, "Slug must be at most 20 characters long"),
|
||||
// slug: z
|
||||
// .string()
|
||||
// .regex(/^[a-zA-Z0-9_-]*$/, "Slug can only contain letters, numbers, underscores, and hyphens")
|
||||
// .min(5, "Slug must be at least 5 characters long")
|
||||
// .max(20, "Slug must be at most 20 characters long"),
|
||||
});
|
||||
|
||||
export type OrganizationSchema = z.infer<typeof OrganizationSchema>;
|
||||
|
||||
Reference in New Issue
Block a user