Working on some bugs.

This commit is contained in:
charlesgauthereau
2025-07-27 21:26:31 +02:00
parent f9dc15ba73
commit 85047e7509
107 changed files with 971 additions and 2203 deletions
@@ -9,9 +9,9 @@ import { toast } from "sonner";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import Link from "next/link";
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
import { LoginSchema, LoginType } from "@/components/wrappers/auth/login/loginForm/login-form.schema";
import { SocialAuthButton, SocialProviderType } from "@/components/wrappers/auth/login/buttonAuth/SocialAuthButton";
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
import { LoginSchema, LoginType } from "@/components/wrappers/auth/login/login-form/login-form.schema";
import { SocialAuthButton, SocialProviderType } from "@/components/wrappers/auth/login/button-auth/social-auth-button";
import { signIn } from "@/lib/auth/auth-client";
import { useRouter } from "next/navigation";
import { Icon } from "@iconify/react";
@@ -12,7 +12,7 @@ import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { TooltipProvider, TooltipTrigger, Tooltip, TooltipContent } from "@/components/ui/tooltip";
import { RegisterSchema, RegisterType } from "@/components/wrappers/auth/register/register-form/register-form.schema";
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
import {signUp} from "@/lib/auth/auth-client";
export type registerFormProps = {
@@ -1,7 +1,7 @@
"use server";
import { userAction } from "@/safe-actions";
import { z } from "zod";
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.schema";
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.schema";
import { eq } from "drizzle-orm";
import { db } from "@/db";
import * as drizzleDb from "@/db";
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import { EmailFormSchema, EmailFormType } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.schema";
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
import { updateEmailSettingsAction } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.action";
import { EmailFormSchema, EmailFormType } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.schema";
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
import { updateEmailSettingsAction } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.action";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
@@ -1,4 +1,4 @@
import { EmailForm } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/EmailForm";
import { EmailForm } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form";
import { Send } from "lucide-react";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
import { useMutation } from "@tanstack/react-query";
@@ -2,14 +2,14 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Info, ShieldCheck } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { StorageS3Form } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/StorageS3Form";
import { StorageS3Form } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/storage-s3-form";
import { useState } from "react";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
import { useMutation } from "@tanstack/react-query";
import { checkConnexionToS3 } from "@/features/upload/public/upload.action";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { updateStorageSettingsAction } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.action";
import { updateStorageSettingsAction } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.action";
import {Setting} from "@/db/schema/00_setting";
export type SettingsStorageTabProps = {
@@ -3,7 +3,7 @@
import { userAction } from "@/safe-actions";
import { z } from "zod";
import { db } from "@/db";
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.schema";
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.schema";
import { eq } from "drizzle-orm";
import * as drizzleDb from "@/db";
@@ -8,10 +8,10 @@ import { Button } from "@/components/ui/button";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.schema";
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.schema";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.action";
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.action";
export type S3FormProps = {
defaultValues?: S3FormType;
@@ -1,8 +1,8 @@
"use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/AdminEmailTab/SettingsEmailTab";
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/AdminStorageTab/SettingsStorageTab";
import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab";
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab";
import { AdminUsersTable } from "@/components/wrappers/dashboard/admin/admin-user-table";
import { User } from "@/db/schema/01_user";
import { Setting } from "@/db/schema/00_setting";
@@ -1,13 +1,13 @@
"use client"
import {ColumnDef} from "@tanstack/react-table";
import {Badge} from "@/components/ui/badge";
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
import {updateUserAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
import {useMutation} from "@tanstack/react-query";
import {toast} from "sonner";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {Trash2} from "lucide-react";
import {deleteUserAction} from "@/components/wrappers/dashboard/profile/ButtonDeleteAccount/delete-account.action";
import {deleteUserAction} from "@/components/wrappers/dashboard/profile/button-delete-account/delete-account.action";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {User} from "@/db/schema/01_user";
import {useSession} from "@/lib/auth/auth-client";
@@ -1,7 +1,7 @@
"use client";
import {generateEdgeKey} from "@/utils/edge_key";
import {getServerUrl} from "@/utils/get-server-url";
import {PasswordInput} from "@/components/wrappers/auth/PaswordInput/password-input";
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
import {useState} from "react";
import {CopyButton} from "@/components/wrappers/common/button/copy-button";
import {Agent} from "@/db/schema/07_agent";
@@ -16,7 +16,7 @@ export const AgentCard = (props: agentCardProps) => {
return (
<Link href={`/dashboard/agents/${agent.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
<Card className="flex flex-row justify-between">
<div>
<div className="flex-1 text-left">
<CardHeader className="text-2xl font-bold">{agent.name}</CardHeader>
<CardContent>Last contact: {formatDateLastContact(agent.lastContact)}</CardContent>
</div>
@@ -1,6 +1,6 @@
"use server";
import {ActionError, userAction} from "@/safe-actions";
import {AgentSchema} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.schema";
import {AgentSchema} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.schema";
import {z} from "zod";
import {eq, and, ne, count} from "drizzle-orm";
import {db} from "@/db";
@@ -16,9 +16,9 @@ import {Button} from "@/components/ui/button";
import {useRouter} from "next/navigation";
import {useMutation} from "@tanstack/react-query";
import {TooltipProvider} from "@/components/ui/tooltip";
import {AgentSchema, AgentType} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.schema";
import {AgentSchema, AgentType} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.schema";
import {toast} from "sonner";
import {createAgentAction, updateAgentAction} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.action";
import {createAgentAction, updateAgentAction} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.action";
export type agentFormProps = {
defaultValues?: AgentType;
@@ -7,7 +7,7 @@ import { generateEdgeKey } from "@/utils/edge_key";
import { PropsWithChildren } from "react";
import { CopyButton } from "@/components/wrappers/common/button/copy-button";
import { getServerUrl } from "@/utils/get-server-url";
import { CodeSnippet } from "@/components/wrappers/code-snippet/CodeSnippet";
import { CodeSnippet } from "@/components/wrappers/code-snippet/code-snippet";
import {Agent} from "@/db/schema/07_agent";
export type agentRegistrationDialogProps = PropsWithChildren<{
@@ -0,0 +1,42 @@
"use client";
import { Trash2 } from "lucide-react";
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import {deleteAgentAction} from "@/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action";
export type ButtonDeleteAgentProps = {
text?: string;
agentId: string;
};
export const ButtonDeleteAgent = (props: ButtonDeleteAgentProps) => {
const router = useRouter();
const mutation = useMutation({
mutationFn: () => deleteAgentAction(props.agentId),
onSuccess: async (result: any) => {
if (result.data?.success) {
toast.success(result.data.actionSuccess.message);
router.push("/dashboard/agents");
} else {
toast.error(result.data.actionError.message || "Unknown error occurred.");
}
},
});
return (
<ButtonWithConfirm
text={props.text ? props.text : ""}
onClick={() => {
mutation.mutate();
}}
variant={"destructive"}
isPending={mutation.isPending}
className="gap-2"
icon={<Trash2 />}
/>
);
};
@@ -0,0 +1,53 @@
"use server";
import {userAction} from "@/safe-actions";
import {z} from "zod";
import {v4 as uuidv4} from "uuid";
import {ServerActionResult} from "@/types/action-type";
import {eq} from "drizzle-orm";
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {Agent} from "@/db/schema/07_agent";
export const deleteAgentAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise<ServerActionResult<Agent>> => {
try {
// const deletedAgent: Agent[] = await db.delete(drizzleDb.schemas.agent).where(eq(drizzleDb.schemas.agent.id, parsedInput)).returning();
const uuid = uuidv4();
const updatedAgent = await db
.update(drizzleDb.schemas.agent)
.set({
isArchived: true,
slug: uuid,
})
.where(eq(drizzleDb.schemas.agent.id, parsedInput))
.returning();
if (!updatedAgent[0]) {
throw new Error("Agent not found or update failed");
}
return {
success: true,
value: updatedAgent[0],
actionSuccess: {
message: "Agent has been successfully deleted.",
messageParams: {projectId: parsedInput},
},
};
} catch (error) {
return {
success: false,
actionError: {
message: "Failed to delete agent.",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: {projectId: parsedInput},
},
};
}
});
@@ -1,8 +1,8 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { ChevronUp } from "lucide-react";
import { LoggedInDropdown } from "@/components/wrappers/dashboard/loggedInDropdown/LoggedInDropdown";
import { currentUser } from "@/lib/auth/current-user";
import {LoggedInDropdown} from "@/components/wrappers/dashboard/common/logged-in/logged-in-dropdown";
export const LoggedInButton = async () => {
const user = await currentUser();
@@ -8,8 +8,8 @@ import {
import {SidebarLogo} from "@/components/wrappers/dashboard/common/sidebar/logo-sidebar";
import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar-main";
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
import {LoggedInButton} from "@/components/wrappers/dashboard/common/logged-in/logged-in-button";
export function AppSidebar() {
@@ -15,6 +15,7 @@ export const SidebarMenuCustomMain = () => {
const member = authClient.useActiveMember();
if (isPending) return null;
if (error || !session) {
@@ -2,7 +2,7 @@
import { Clock9 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { CronInput } from "@/components/wrappers/dashboard/database/CronButton/CronInput";
import { CronInput } from "@/components/wrappers/dashboard/database/cron-button/cron-input";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
@@ -10,7 +10,7 @@ import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action";
import {Database} from "@/db/schema/06_database";
export type CronButtonProps = {
@@ -1,5 +1,5 @@
import { AdvancedCronSelect } from "./AdvancedCronSelect";
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
import { AdvancedCronSelect } from "./advanced-cron-select";
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useRouter } from "next/navigation";
@@ -8,8 +8,8 @@ import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import { DatabaseSchema, DatabaseType } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
import { updateDatabaseAction } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.action";
import { DatabaseSchema, DatabaseType } from "@/components/wrappers/dashboard/database/database-form/form-database.schema";
import { updateDatabaseAction } from "@/components/wrappers/dashboard/database/database-form/form-database.action";
import { toast } from "sonner";
export type DatabaseFormProps = {
@@ -4,7 +4,7 @@ import { z } from "zod";
import { userAction } from "@/safe-actions";
import { db } from "@/db";
import { eq } from "drizzle-orm";
import { DatabaseSchema } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
import { DatabaseSchema } from "@/components/wrappers/dashboard/database/database-form/form-database.schema";
import * as drizzleDb from "@/db";
export const updateDatabaseAction = userAction
@@ -4,6 +4,8 @@ import {ComboBox} from "@/components/wrappers/common/combobox";
import {useSidebar} from "@/components/ui/sidebar";
import {useRouter} from "next/navigation";
import {authClient} from "@/lib/auth/auth-client";
import {auth} from "@/lib/auth/auth";
export function OrganizationCombobox() {
const router = useRouter();
@@ -24,11 +26,15 @@ export function OrganizationCombobox() {
};
});
const onValueChange = async (slug: string) => {
await authClient.organization.setActive({
organizationSlug: slug,
});
router.refresh();
};
const handleReset = () => {
@@ -7,7 +7,7 @@ export const OrganizationFormSchema = z.object({
.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'),
members: z.array(z.string()),
users: z.array(z.string()),
});
@@ -1,19 +1,38 @@
"use client";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
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 {Card, CardContent, CardHeader} from "@/components/ui/card";
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
useZodForm
} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Form} from "@/components/ui/form";
import {Button} from "@/components/ui/button";
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/organization-form/organization-form.schema";
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/02_organization";
import {
deleteOrganizationAction,
updateOrganizationAction
} from "@/components/wrappers/dashboard/organization/organization.action";
import {toast} from "sonner";
import {User as BetterAuthUser} from "better-auth";
import {User} from "@/db/schema/01_user";
export type organizationFormProps = {
defaultValues?: OrganizationWithMembers;
members: MemberWithUser[];
users: User[];
currentUser: BetterAuthUser;
};
export const OrganizationForm = (props: organizationFormProps) => {
@@ -21,17 +40,20 @@ export const OrganizationForm = (props: organizationFormProps) => {
const router = useRouter();
const isCreate = !Boolean(props.defaultValues);
const formatUsersList = (members: MemberWithUser[]) => {
const formatUsersList = (users: User[]) => {
return members.map((member) => ({
value: member.user.id,
label: `${member.user.name} | ${member.user.email}`,
}));
return users
.filter((user) => user.id !== props.currentUser.id)
.map((user) => ({
value: user.id,
label: `${user.name} | ${user.email}`,
}));
};
const formatDefaultUsers = (members: MemberWithUser[]): string[] => {
console.log("ici",members);
return members.map((member) => member.userId);
return members
.filter((member) => member.userId !== props.currentUser.id)
.map((member) => member.userId);
};
const formattedDefaultValues = {
@@ -46,21 +68,28 @@ export const OrganizationForm = (props: organizationFormProps) => {
});
const mutation = useMutation({
mutationFn: async (values: OrganizationFormType) => {
console.log(values);
// const organization = await updateOrganizationAction({ data: values, organizationId: props.defaultValues.id });
// console.log(organization);
// if (organization.data.success) {
// // toast.success(organization.data.actionSuccess.message);
// // router.push(`/dashboard/${organization.data.value.slug}/settings`);
// // router.refresh();
// } else {
// // toast.success(organization.data.actionError.message);
// }
mutationFn: (values: OrganizationFormType) => updateOrganizationAction({
data: values,
organizationId: props.defaultValues?.id ?? ""
}),
onSuccess: async (result) => {
if (result?.data?.success) {
toast.success(result.data.actionSuccess?.message || "Organization updated successfully.");
router.push("/dashboard/settings");
} else {
// @ts-ignore
const errorMsg = result?.data?.actionError?.message || result?.data?.actionError?.messageParams?.message || "Failed to update the organization.";
toast.error(errorMsg);
}
},
onError: (error: any) => {
console.error("Mutation network error:", error);
toast.error(error?.message || "A network error occurred.");
},
});
console.log(formatUsersList(props.members))
return (
<Card>
@@ -77,13 +106,13 @@ export const OrganizationForm = (props: organizationFormProps) => {
control={form.control}
name="name"
defaultValue=""
render={({ field }) => (
render={({field}) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Organization 1" {...field} />
</FormControl>
<FormMessage />
<FormMessage/>
</FormItem>
)}
/>
@@ -91,7 +120,7 @@ export const OrganizationForm = (props: organizationFormProps) => {
control={form.control}
name="slug"
defaultValue=""
render={({ field }) => (
render={({field}) => (
<FormItem>
<FormLabel>Slug</FormLabel>
<FormControl>
@@ -104,19 +133,19 @@ export const OrganizationForm = (props: organizationFormProps) => {
}}
/>
</FormControl>
<FormMessage />
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="users"
render={({ field }) => (
render={({field}) => (
<FormItem>
<FormLabel>Users</FormLabel>
<FormControl>
<MultiSelect
options={formatUsersList(props.members)}
options={formatUsersList(props.users)}
onValueChange={field.onChange}
defaultValue={field.value ?? []}
placeholder="Select users"
@@ -126,7 +155,7 @@ export const OrganizationForm = (props: organizationFormProps) => {
/>
</FormControl>
<FormDescription>Select users you want to add to this organization</FormDescription>
<FormMessage />
<FormMessage/>
</FormItem>
)}
/>
@@ -4,14 +4,14 @@ import { userAction } from "@/safe-actions";
import { OrganizationSchema } from "@/components/wrappers/dashboard/organization/organization.schema";
import { ServerActionResult } from "@/types/action-type";
import { z } from "zod";
import { v4 as uuidv4 } from "uuid";
import { OrganizationFormSchema } from "@/components/wrappers/dashboard/organization/OrganizationForm/organization-form.schema";
import { OrganizationFormSchema } from "@/components/wrappers/dashboard/organization/organization-form/organization-form.schema";
import { db } from "@/db";
import { eq } from "drizzle-orm";
import {checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth";
import {and, eq, inArray} from "drizzle-orm";
import {auth, checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth";
import {slugify} from "@/utils/slugify";
import {Organization} from "@/db/schema/02_organization";
import * as drizzleDb from "@/db";
import {headers} from "next/headers";
export const createOrganizationAction = userAction.schema(OrganizationSchema).action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
try {
@@ -72,53 +72,117 @@ export const updateOrganizationAction = userAction
organizationId: z.string(),
})
)
.action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
.action(async ({ parsedInput, ctx }): Promise<ServerActionResult<Organization>> => {
try {
const newUserList = parsedInput.data.users;
console.log(parsedInput);
const organization = await db.query.organization.findFirst({
where: eq(drizzleDb.schemas.organization.id, parsedInput.organizationId),
with: {
members: true,
}
});
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.");
if (!organization) {
return {
success: false,
actionError: {
message: "Organization not found.",
status: 404,
cause: "not_found",
},
};
}
const existingItemIds = organization[0].users.map((user) => user.userId);
const existingItemIds = organization.members
.filter((member) => member.userId !== ctx.user.id)
.map((member) => member.userId);
const usersToAdd = newUserList.filter((id) => !existingItemIds.includes(id));
const usersToRemove = existingItemIds.filter((id) => !newUserList.includes(id));
if (usersToAdd.length > 0) {
await db
.insert(drizzleDb.schemas.organization)
.values(
usersToAdd.map((userId) => ({
userId,
organizationId: organization[0].id,
for (const userToAdd of usersToAdd) {
await auth.api.addMember({
body: {
userId: userToAdd,
role: "member",
}))
)
.execute();
organizationId: organization.id,
},
});
}
//
// await db
// .insert(drizzleDb.schemas.member)
// .values(
// usersToAdd.map((userId) => ({
// userId,
// organizationId: organization.id,
// role: "member",
// }))
// )
// .execute();
}
if (usersToRemove.length > 0) {
await db.delete().from(userOrganization).where(inArray(userOrganization.userId, usersToRemove)).execute();
await db.delete(drizzleDb.schemas.member).where(inArray(drizzleDb.schemas.member.userId, usersToRemove)).execute();
// TODO : Do not delete, go permission error with better auth
// for (const userToRemove of usersToRemove) {
//
// const memberToRemove = await db.query.member.findFirst({
// where: and(eq(drizzleDb.schemas.member.userId, userToRemove), eq(drizzleDb.schemas.member.organizationId, organization.id)),
// with: {
// user: true
// }
// })
// console.log(memberToRemove)
//
// if (memberToRemove) {
// console.log("ici")
// await auth.api.removeMember({
// body: {
// memberIdOrEmail: memberToRemove.user.email,
// organizationId: organization.id,
// },
// headers: await headers()
// });
// }
//
// }
}
// const updatedOrganization = await auth.api.updateOrganization({
// body: {
// data: {
// name: parsedInput.data.name,
// slug: parsedInput.data.slug,
// },
// organizationId: organization.id,
// },
// headers: await headers(),
// });
const updatedOrganization = await db
.update(organization)
.update(drizzleDb.schemas.organization)
.set({
name: parsedInput.data.name,
slug: parsedInput.data.slug,
})
.where(eq(organization.id, parsedInput.organizationId))
.where(eq(drizzleDb.schemas.organization.id, parsedInput.organizationId))
.returning()
.execute();
return {
success: true,
value: updatedOrganization[0],
value: updatedOrganization as unknown as Organization,
actionSuccess: {
message: "Organization has been successfully updated.",
messageParams: { organizationId: updatedOrganization[0].id },
messageParams: { organizationId: organization.id},
},
};
} catch (error) {
@@ -128,7 +192,7 @@ export const updateOrganizationAction = userAction
actionError: {
message: "Failed to update organization.",
status: 500,
cause: error.message ?? "Unknown error",
cause: "server_error",
messageParams: { message: "Error updating the organization" },
},
};
@@ -4,7 +4,7 @@ import { UploadIcon } from "lucide-react";
import { toast } from "sonner";
import { uploadImageAction } from "@/features/upload/public/upload.action";
import { useMutation } from "@tanstack/react-query";
import { updateImageUserAction } from "@/components/wrappers/dashboard/profile/Avatar/avatar.action";
import { updateImageUserAction } from "@/components/wrappers/dashboard/profile/avatar/avatar.action";
import { useRouter } from "next/navigation";
import { User } from "@/db/schema/01_user";
@@ -1,7 +1,7 @@
"use server";
import { userAction } from "@/safe-actions";
import { z } from "zod";
import { UserSchema } from "@/components/wrappers/dashboard/profile/UserForm/user-form.schema";
import { UserSchema } from "@/components/wrappers/dashboard/profile/user-form/user-form.schema";
import { db } from "@/db";
import { eq } from "drizzle-orm";
import * as drizzleDb from "@/db";
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { TooltipProvider } from "@/components/ui/tooltip";
import { UserSchema, UserType } from "@/components/wrappers/dashboard/profile/UserForm/user-form.schema";
import { UserSchema, UserType } from "@/components/wrappers/dashboard/profile/user-form/user-form.schema";
import { toast } from "sonner";
import { updateUserAction } from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
import { updateUserAction } from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
export type userFormProps = {
defaultValues?: UserType;
@@ -3,7 +3,7 @@
import { Trash2 } from "lucide-react";
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
import { useMutation } from "@tanstack/react-query";
import { deleteProjectAction } from "@/components/wrappers/dashboard/projects/ButtonDeleteProject/delete-project.action";
import { deleteProjectAction } from "@/components/wrappers/dashboard/projects/button-delete-project/delete-project.action";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
@@ -1,7 +1,7 @@
"use server";
import { userAction } from "@/safe-actions";
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/ProjectsForm/ProjectForm.schema";
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
import { z } from "zod";
import { ServerActionResult } from "@/types/action-type";
import { db } from "@/db";
@@ -6,8 +6,8 @@ import { Input } from "@/components/ui/input";
import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { useMutation } from "@tanstack/react-query";
import { ProjectSchema, ProjectType } from "@/components/wrappers/dashboard/projects/ProjectsForm/ProjectForm.schema";
import { createProjectAction, updateProjectAction } from "@/components/wrappers/dashboard/projects/ProjectsForm/project-form.action";
import { ProjectSchema, ProjectType } from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
import { createProjectAction, updateProjectAction } from "@/components/wrappers/dashboard/projects/project-form/project-form.action";
import { useRouter } from "next/navigation";
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
import { toast } from "sonner";
@@ -5,7 +5,7 @@ import {MemberWithUser} from "@/db/schema/02_organization";
import {useState} from "react";
import {useSession} from "@/lib/auth/auth-client";
import {useMutation} from "@tanstack/react-query";
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
import {updateUserAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
import {toast} from "sonner";
import {Badge} from "@/components/ui/badge";
@@ -1,19 +0,0 @@
"use client";
import { useSidebar } from "@/components/ui/sidebar";
import { env } from "@/env.mjs";
export type SideBarFooterCreditProps = {};
export const SideBarFooterCredit = (props: SideBarFooterCreditProps) => {
const { state } = useSidebar();
return (
<>
{state === "expanded" && (
<div className="text-center">
<h1 className="text-[10px]">Portabase Community Edition v{env.NEXT_PUBLIC_PROJECT_VERSION}</h1>
</div>
)}
</>
);
};
@@ -1,15 +0,0 @@
"use client"
import {useSidebar} from "@/components/ui/sidebar";
export type SideBarLogoProps = {}
export const SideBarLogo = (props: SideBarLogoProps) => {
const { state, isMobile } = useSidebar();
return (
<div className="flex justify-center items-center ">
<h1 className="font-bold text-xl">
{state === 'collapsed' && !isMobile ? "P" : "Portabase"}
</h1>
</div>
)
}
@@ -1,162 +0,0 @@
"use client";
import { JSX, useEffect, useState } from "react";
import Link from "next/link";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu as SM,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { ChevronDown, MoreHorizontal } from "lucide-react";
import { usePathname } from "next/navigation";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
export type SidebarMenuProps = {
baseUrl: string;
items: SidebarItem[];
};
type SidebarItemContent = {
title: string;
url: string;
icon: JSX.Element;
dropdown?: {
title: string;
url: string;
}[];
};
type SidebarListItem = {
type: "list";
content: SidebarItemContent;
};
type SidebarCollapseItem = {
type: "collapse";
title: string;
content: SidebarItemContent[];
};
export type SidebarItem = SidebarListItem | SidebarCollapseItem;
export const SidebarMenu = (props: SidebarMenuProps) => {
const pathname = usePathname();
useEffect(() => {
const currentUrl = pathname;
const currentItem = props.items.find((item) => {
if (item.type === "list") {
return `${props.baseUrl}/${item.content.url}` === currentUrl;
} else if (item.type === "collapse") {
return item.content.some((content) => `${props.baseUrl}/${content.url}` === currentUrl);
}
return false;
});
if (currentItem) {
if (currentItem.type === "list") {
setActiveItem(currentItem.content.title);
} else if (currentItem.type === "collapse") {
setActiveItem(currentItem.title!);
}
} else {
setActiveItem("");
}
}, [pathname]);
const [activeItem, setActiveItem] = useState("");
const handleItemClick = (title: string) => {
setActiveItem(title);
};
return (
<SM>
{props.items.map((item, index) =>
item.type === "collapse" ? (
<Collapsible defaultOpen key={index} className={`group/collapsible`}>
<SidebarGroup>
<SidebarGroupLabel asChild>
<CollapsibleTrigger>
{item.title ?? "Please define a title"}
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
{item.content.map((content) => (
<SidebarMenuItem key={content.title}>
<SidebarMenuButton asChild>
<Link
className={cn(
buttonVariants({
size: "lg",
variant: activeItem === content.title ? "secondary" : "ghost",
}),
"justify-start p-0"
)}
href={`${props.baseUrl}/${content.url}`}
onClick={() => handleItemClick(content.title)}
>
{content.icon}
<span>{content.title}</span>
</Link>
</SidebarMenuButton>
{content.dropdown && (
<SidebarMenuAction>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction>
<MoreHorizontal />
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent side="right" align="start">
{content.dropdown.map((dropdown) => (
<Link href={`${props.baseUrl}/${dropdown.url}`} className="justify-start p-0">
<DropdownMenuItem>
<span>{dropdown.title ?? "Please define a title"}</span>
</DropdownMenuItem>
</Link>
))}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuAction>
)}
</SidebarMenuItem>
))}
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
) : (
<SidebarMenuItem key={index}>
<SidebarMenuButton asChild>
<Link
className={cn(
buttonVariants({
size: "lg",
variant: activeItem === item.content.title ? "secondary" : "ghost",
}),
"justify-start p-0"
)}
href={`${props.baseUrl}/${item.content.url}`}
onClick={() => handleItemClick(item.content.title)}
>
{item.content.icon}
<span>{item.content.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)
)}
</SM>
);
};
@@ -1,54 +0,0 @@
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu as SM,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
import {SideBarLogo} from "@/components/wrappers/dashboard/sideBar/SideBarLogo/SideBarLogo";
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/sideBar/SideBarFooterCredit/SideBarFooterCredit";
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
import {SidebarContentA} from "./sidebar-content";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {notFound} from "next/navigation";
export async function AppSidebar() {
const member = await getActiveMember();
console.log("member", member);
if (!member) {
return notFound();
}
// const organization = await getOrganization({organizationId: member.organizationId});
// console.log("organization", organization);
return (
<Sidebar collapsible="icon">
<SidebarHeader>
<SideBarLogo/>
<SM>
<SidebarMenuItem>
<OrganizationCombobox/>
</SidebarMenuItem>
</SM>
</SidebarHeader>
<SidebarContent>
<SidebarContentA/>
</SidebarContent>
<SidebarFooter>
<SM>
<SidebarMenuItem>
<LoggedInButton/>
</SidebarMenuItem>
</SM>
<SideBarFooterCredit/>
</SidebarFooter>
</Sidebar>
);
}
@@ -1,87 +0,0 @@
"use client";
import { SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarContent } from "@/components/ui/sidebar";
import { authClient, useSession } from "@/lib/auth/auth-client";
import { Layers, ChartArea, Settings, ShieldHalf } from "lucide-react";
import { SidebarItem, SidebarMenu } from "./SideBarMenu/SideBarMenu";
export const SidebarContentA = () => {
const { data: activeOrganization } = authClient.useActiveOrganization();
const { data: organizations } = authClient.useListOrganizations();
const { data: session } = useSession();
const member = authClient.useActiveMember(); // Moved here — always called
const appItems: SidebarItem[] = [
{
type: "list",
content: {
title: "Projects",
url: "projects",
icon: <Layers />,
},
},
{
type: "list",
content: {
title: "Statistics",
url: "statistics",
icon: <ChartArea />,
},
},
];
if (activeOrganization && member?.data?.role === "admin" || member?.data?.role === "owner") {
appItems.push({
type: "list",
content: {
title: "Settings",
url: "settings",
icon: <Settings />,
},
});
}
const adminItems: SidebarItem[] = [
{
type: "list",
content: {
title: "Agents",
url: "agents",
icon: <ShieldHalf />,
},
},
{
type: "list",
content: {
title: "Administration panel",
url: "admin",
icon: <Settings />,
},
},
];
return (
activeOrganization && (
<>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu items={appItems} baseUrl={`/dashboard`} />
</SidebarGroupContent>
</SidebarGroup>
{(session && (session.user.role === "admin" || session.user.role === "superadmin")) && (
<SidebarGroup>
<SidebarGroupLabel>Administration</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu items={adminItems} baseUrl={`/dashboard`} />
</SidebarGroupContent>
</SidebarGroup>
)}
</>
)
);
};