@@ -95,4 +115,4 @@ export default async function RoutePage(props: PageParams<{
);
-}
+}
\ No newline at end of file
diff --git a/app/(customer)/dashboard/(organization)/projects/new/page.tsx b/app/(customer)/dashboard/(organization)/projects/new/page.tsx
deleted file mode 100644
index 7cfb3f1d..00000000
--- a/app/(customer)/dashboard/(organization)/projects/new/page.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import {PageParams} from "@/types/next";
-import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
-import {ProjectForm} from "@/components/wrappers/dashboard/projects/project-form/project-form";
-import {notFound} from "next/navigation";
-import {db} from "@/db";
-import {eq} from "drizzle-orm";
-import {getOrganization} from "@/lib/auth/auth";
-import * as drizzleDb from "@/db";
-import {DatabaseWith} from "@/db/schema/07_database";
-
-
-export default async function RoutePage(props: PageParams<{}>) {
-
- const organization = await getOrganization({});
-
- if (!organization) {
- notFound();
- }
-
- const availableDatabases = (
- await db.query.database.findMany({
- where: (db, {isNull}) => isNull(db.projectId),
- with: {
- agent: true,
- project: true,
- backups: true,
- restorations: true,
- },
- orderBy: (db, {desc}) => [desc(db.createdAt)],
- })
- ).filter((db) => db.project == null) as DatabaseWith[];
-
- const org = await db.query.organization.findFirst({
- where: eq(drizzleDb.schemas.organization.slug, organization.slug),
- });
-
- if (!org) notFound();
-
-
- return (
-
-
- Create new project
-
-
-
-
-
- );
-}
diff --git a/app/(customer)/dashboard/(organization)/projects/page.tsx b/app/(customer)/dashboard/(organization)/projects/page.tsx
index 3160ea9d..395a1c4d 100644
--- a/app/(customer)/dashboard/(organization)/projects/page.tsx
+++ b/app/(customer)/dashboard/(organization)/projects/page.tsx
@@ -1,5 +1,3 @@
-import Link from "next/link";
-
import {PageParams} from "@/types/next";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {Button} from "@/components/ui/button";
@@ -10,6 +8,8 @@ import {notFound} from "next/navigation";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {Metadata} from "next";
+import {ProjectDialog} from "@/features/projects/components/project.dialog";
+import {DatabaseWith} from "@/db/schema/07_database";
export const metadata: Metadata = {
title: "Projects",
@@ -36,6 +36,19 @@ export default async function RoutePage(props: PageParams<{}>) {
});
const isMember = activeMember?.role === "member";
+ const availableDatabases = (
+ await db.query.database.findMany({
+ where: (db, {isNull}) => isNull(db.projectId),
+ with: {
+ agent: true,
+ project: true,
+ backups: true,
+ restorations: true,
+ },
+ orderBy: (db, {desc}) => [desc(db.createdAt)],
+ })
+ ).filter((db) => db.project == null) as DatabaseWith[];
+
return (
@@ -43,9 +56,9 @@ export default async function RoutePage(props: PageParams<{}>) {
Projects
{(projects.length > 0 && !isMember) && (
-
+
-
+
)}
@@ -62,9 +75,11 @@ export default async function RoutePage(props: PageParams<{}>) {
) : isMember ? (
) : (
-
+
+
+
)}
);
-}
+}
\ No newline at end of file
diff --git a/app/(customer)/dashboard/(organization)/settings/edit/page.tsx b/app/(customer)/dashboard/(organization)/settings/edit/page.tsx
deleted file mode 100644
index 3d0ba2c8..00000000
--- a/app/(customer)/dashboard/(organization)/settings/edit/page.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import {PageParams} from "@/types/next";
-import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
-import {notFound} from "next/navigation";
-import {OrganizationForm} from "@/components/wrappers/dashboard/organization/organization-form/organization-form";
-import {getOrganization} from "@/lib/auth/auth";
-import {db} from "@/db";
-import {isNull} from "drizzle-orm";
-import {currentUser} from "@/lib/auth/current-user";
-
-export default async function RoutePage(props: PageParams<{
-
-}>) {
- const user = await currentUser();
-
- const organization = await getOrganization({});
-
- const users = await db.query.user.findMany({
- where: (fields) => isNull(fields.deletedAt)
- });
-
- if (!user || !users || !organization || organization.slug == "default") {
- notFound();
- }
-
- return(
-
-
-
- Edit {organization.name}
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/app/(customer)/dashboard/(organization)/settings/page.tsx b/app/(customer)/dashboard/(organization)/settings/page.tsx
index 02bc5d12..0c3382be 100644
--- a/app/(customer)/dashboard/(organization)/settings/page.tsx
+++ b/app/(customer)/dashboard/(organization)/settings/page.tsx
@@ -9,9 +9,13 @@ import {getOrganizationChannels} from "@/db/services/notification-channel";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
import {DeleteOrganizationButton} from "@/components/wrappers/dashboard/organization/delete-organization-button";
-import {
- EditButtonSettings
-} from "@/components/wrappers/dashboard/organization/settings/edit-button-settings/edit-button-settings";
+import {EditOrganizationDialog} from "@/features/organization/components/edit-organization.dialog";
+import {Button} from "@/components/ui/button";
+import {GearIcon} from "@radix-ui/react-icons";
+import {db} from "@/db";
+import {isNull} from "drizzle-orm";
+import * as drizzleDb from "@/db";
+import {eq} from "drizzle-orm";
export const metadata: Metadata = {
title: "Settings",
@@ -22,7 +26,7 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
const user = await currentUser();
const activeMember = await getActiveMember()
- if (!organization || !activeMember) {
+ if (!organization || !activeMember || !user) {
notFound();
}
@@ -30,6 +34,30 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
const storageChannels = await getOrganizationStorageChannels(organization.id)
const permissions = computeOrganizationPermissions(activeMember);
+ // Fetch users for the form
+ const users = await db.query.user.findMany({
+ where: (fields) => isNull(fields.deletedAt)
+ });
+
+ // Re-fetch organization with members to match type expectations if needed,
+ // although getOrganization returns OrganizationWithMembers usually, let's verify or cast.
+ // getOrganization returns Organization type from database schema, which includes relations if defined in auth lib.
+ // However, OrganizationForm expects OrganizationWithMembers.
+ // Let's ensure we have members.
+
+ const organizationWithMembers = await db.query.organization.findFirst({
+ where: eq(drizzleDb.schemas.organization.id, organization.id),
+ with: {
+ members: {
+ with: {
+ user: true
+ }
+ }
+ }
+ });
+
+ if (!organizationWithMembers) notFound();
+
return (
@@ -41,7 +69,15 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
{permissions.canManageSettings && organization.slug !== "default" && (
-
+
+
+
)}
@@ -62,4 +98,4 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
)
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 6ef1a702..7f48ede0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "portabase",
- "version": "1.2.3",
+ "version": "1.2.4-rc.1",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 8887",
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 473b84ca..8b7de8d6 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -58,7 +58,7 @@ function DialogContent({
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
- "max-h-[90vh] overflow-y-auto",
+ "max-h-[90vh] overflow-y-auto overflow-x-hidden",
className
)}
{...props}
diff --git a/src/components/wrappers/common/empty-state-placeholder.tsx b/src/components/wrappers/common/empty-state-placeholder.tsx
index a7e482c5..3339053c 100644
--- a/src/components/wrappers/common/empty-state-placeholder.tsx
+++ b/src/components/wrappers/common/empty-state-placeholder.tsx
@@ -1,20 +1,22 @@
import Link from "next/link";
import {cn} from "@/lib/utils";
import {Plus} from "lucide-react";
+import {forwardRef} from "react";
type EmptyStatePlaceholderProps = {
url?: string;
onClick?: () => void;
text: string;
className?: string;
-};
+} & React.HTMLAttributes
;
-export const EmptyStatePlaceholder = ({
+export const EmptyStatePlaceholder = forwardRef(({
url,
onClick,
text,
className,
- }: EmptyStatePlaceholderProps) => {
+ ...props
+ }, ref) => {
const Container = (
{Container}
;
-};
+ return {Container}
;
+});
+
+EmptyStatePlaceholder.displayName = "EmptyStatePlaceholder";
\ No newline at end of file
diff --git a/src/components/wrappers/common/multiselect/multi-select.tsx b/src/components/wrappers/common/multiselect/multi-select.tsx
index cb679e06..4e533301 100644
--- a/src/components/wrappers/common/multiselect/multi-select.tsx
+++ b/src/components/wrappers/common/multiselect/multi-select.tsx
@@ -15,7 +15,7 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command
* Variants for the multi-select component to handle different styles.
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
*/
-const multiSelectVariants = cva("m-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300", {
+const multiSelectVariants = cva("m-1 transition ease-in-out duration-300", {
variants: {
variant: {
default: "border-foreground/10 text-foreground bg-card hover:bg-card/80",
@@ -177,13 +177,13 @@ export const MultiSelect = React.forwardRef
return (
{IconComponent && }
- {option?.label}
+ {option?.label}
{
event.stopPropagation();
toggleOption(value);
@@ -196,6 +196,7 @@ export const MultiSelect = React.forwardRef
>
{`+ ${selectedValues.length - maxCount} more`}
{
event.stopPropagation();
clearExtraOptions();
diff --git a/src/components/wrappers/dashboard/admin/organizations/organization/button-delete-organization.tsx b/src/components/wrappers/dashboard/admin/organizations/organization/button-delete-organization.tsx
index 86b4bd97..f1dd75ae 100644
--- a/src/components/wrappers/dashboard/admin/organizations/organization/button-delete-organization.tsx
+++ b/src/components/wrappers/dashboard/admin/organizations/organization/button-delete-organization.tsx
@@ -6,7 +6,7 @@ import {Trash2} from "lucide-react";
import {toast} from "sonner";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
import {authClient} from "@/lib/auth/auth-client";
-import {deleteOrganizationAction} from "@/components/wrappers/dashboard/organization/organization.action";
+import {deleteOrganizationAction} from "@/features/organization/organization.action";
export type ButtonDeleteFleetProps = {
text?: string;
diff --git a/src/components/wrappers/dashboard/admin/organizations/organization/details/update-organization-form.tsx b/src/components/wrappers/dashboard/admin/organizations/organization/details/update-organization-form.tsx
index 9bb6bd4c..00035a45 100644
--- a/src/components/wrappers/dashboard/admin/organizations/organization/details/update-organization-form.tsx
+++ b/src/components/wrappers/dashboard/admin/organizations/organization/details/update-organization-form.tsx
@@ -13,7 +13,7 @@ import {Input} from "@/components/ui/input";
import {authClient} from "@/lib/auth/auth-client";
import {toast} from "sonner";
import {OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
-import {updateOrganizationAction} from "@/components/wrappers/dashboard/organization/organization.action";
+import {updateOrganizationAction} from "@/features/organization/organization.action";
type UpdateOrganizationFormProps = {
onSuccessAction?: () => void;
diff --git a/src/components/wrappers/dashboard/organization/create-organisation-modal.tsx b/src/components/wrappers/dashboard/organization/create-organisation-modal.tsx
index 2cbe3ec4..5532a996 100644
--- a/src/components/wrappers/dashboard/organization/create-organisation-modal.tsx
+++ b/src/components/wrappers/dashboard/organization/create-organisation-modal.tsx
@@ -6,8 +6,8 @@ import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from "@/
import {Button} from "@/components/ui/button";
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
-import {OrganizationSchema} from "@/components/wrappers/dashboard/organization/organization.schema";
-import {createOrganizationAction} from "@/components/wrappers/dashboard/organization/organization.action";
+import {CreateOrganizationSchema, CreateOrganizationType} from "@/features/organization/organization.schema";
+import {createOrganizationAction} from "@/features/organization/organization.action";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {authClient} from "@/lib/auth/auth-client";
@@ -32,12 +32,12 @@ export function CreateOrganizationModal({
const router = useRouter();
const form = useZodForm({
- schema: OrganizationSchema,
+ schema: CreateOrganizationSchema,
});
const mutation = useMutation({
- mutationFn: async (values: OrganizationSchema) => {
+ mutationFn: async (values: CreateOrganizationType) => {
const result = await createOrganizationAction(values);
diff --git a/src/components/wrappers/dashboard/organization/delete-organization-button.tsx b/src/components/wrappers/dashboard/organization/delete-organization-button.tsx
index 6a5dc069..8fb2870f 100644
--- a/src/components/wrappers/dashboard/organization/delete-organization-button.tsx
+++ b/src/components/wrappers/dashboard/organization/delete-organization-button.tsx
@@ -1,6 +1,6 @@
"use client";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
-import {deleteOrganizationAction} from "@/components/wrappers/dashboard/organization/organization.action";
+import {deleteOrganizationAction} from "@/features/organization/organization.action";
import {useMutation} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {toast} from "sonner";
diff --git a/src/components/wrappers/dashboard/organization/organization-form/organization-form.schema.ts b/src/components/wrappers/dashboard/organization/organization-form/organization-form.schema.ts
deleted file mode 100644
index 9d136cc5..00000000
--- a/src/components/wrappers/dashboard/organization/organization-form/organization-form.schema.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import {z} from "zod";
-
-
-export const OrganizationFormSchema = 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'),
- users: z.array(z.string()),
-
-});
-
-export type OrganizationFormType = z.infer;
diff --git a/src/components/wrappers/dashboard/organization/organization.schema.ts b/src/components/wrappers/dashboard/organization/organization.schema.ts
deleted file mode 100644
index 6e2db975..00000000
--- a/src/components/wrappers/dashboard/organization/organization.schema.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-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"),
-});
-
-export type OrganizationSchema = z.infer;
diff --git a/src/components/wrappers/dashboard/organization/settings/edit-button-settings/edit-button-settings.tsx b/src/components/wrappers/dashboard/organization/settings/edit-button-settings/edit-button-settings.tsx
deleted file mode 100644
index 99cb339a..00000000
--- a/src/components/wrappers/dashboard/organization/settings/edit-button-settings/edit-button-settings.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-"use client"
-import {buttonVariants} from "@/components/ui/button";
-import {GearIcon} from "@radix-ui/react-icons";
-import Link from "next/link";
-import {usePathname} from "next/navigation";
-
-export type EditButtonSettings = {}
-
-export const EditButtonSettings= (props:EditButtonSettings) => {
-
- const pathname = usePathname();
-
- return(
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/components/wrappers/dashboard/agent/agent-form/agent-form.action.ts b/src/features/agents/agents.action.ts
similarity index 94%
rename from src/components/wrappers/dashboard/agent/agent-form/agent-form.action.ts
rename to src/features/agents/agents.action.ts
index 77a94df3..93fa782a 100644
--- a/src/components/wrappers/dashboard/agent/agent-form/agent-form.action.ts
+++ b/src/features/agents/agents.action.ts
@@ -1,6 +1,6 @@
"use server";
import {ActionError, userAction} from "@/lib/safe-actions/actions";
-import {AgentSchema} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.schema";
+import {AgentSchema} from "@/features/agents/agents.schema";
import {z} from "zod";
import {eq, and, ne, count} from "drizzle-orm";
import {db} from "@/db";
diff --git a/src/components/wrappers/dashboard/agent/agent-form/agent-form.schema.ts b/src/features/agents/agents.schema.ts
similarity index 74%
rename from src/components/wrappers/dashboard/agent/agent-form/agent-form.schema.ts
rename to src/features/agents/agents.schema.ts
index c05b27ed..2d36a1b5 100644
--- a/src/components/wrappers/dashboard/agent/agent-form/agent-form.schema.ts
+++ b/src/features/agents/agents.schema.ts
@@ -1,7 +1,7 @@
import { z } from "zod";
export const AgentSchema = z.object({
- name: z.string(),
+ name: z.string().nonempty("Name is required"),
description: z.string(),
});
diff --git a/src/features/agents/components/agent.dialog.tsx b/src/features/agents/components/agent.dialog.tsx
new file mode 100644
index 00000000..67d095d6
--- /dev/null
+++ b/src/features/agents/components/agent.dialog.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {AgentForm} from "@/features/agents/components/agent.form";
+import {useState} from "react";
+import {Button} from "@/components/ui/button";
+import {Plus} from "lucide-react";
+import {AgentType} from "@/features/agents/agents.schema";
+
+type AgentDialogProps = {
+ children?: React.ReactNode;
+ agent?: AgentType & { id: string };
+};
+
+export const AgentDialog = ({children, agent}: AgentDialogProps) => {
+ const [open, setOpen] = useState(false);
+ const isEdit = !!agent;
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/wrappers/dashboard/agent/agent-form/agent-form.tsx b/src/features/agents/components/agent.form.tsx
similarity index 90%
rename from src/components/wrappers/dashboard/agent/agent-form/agent-form.tsx
rename to src/features/agents/components/agent.form.tsx
index ee85a77f..8fd796ed 100644
--- a/src/components/wrappers/dashboard/agent/agent-form/agent-form.tsx
+++ b/src/features/agents/components/agent.form.tsx
@@ -1,6 +1,5 @@
"use client";
-import {Card, CardContent} from "@/components/ui/card";
import {
FormControl,
FormDescription,
@@ -16,13 +15,14 @@ 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/agent-form/agent-form.schema";
+import {AgentSchema, AgentType} from "@/features/agents/agents.schema";
import {toast} from "sonner";
-import {createAgentAction, updateAgentAction} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.action";
+import {createAgentAction, updateAgentAction} from "@/features/agents/agents.action";
export type agentFormProps = {
defaultValues?: AgentType;
agentId?: string;
+ onSuccess?: (data: any) => void;
};
export const AgentForm = (props: agentFormProps) => {
@@ -52,7 +52,12 @@ export const AgentForm = (props: agentFormProps) => {
}
toast.success(`Success ${isCreate ? "creating" : "updating"} agent`);
router.refresh();
- router.push(`/dashboard/agents/${data.id}`);
+
+ if (props.onSuccess) {
+ props.onSuccess(data);
+ } else {
+ router.push(`/dashboard/agents/${data.id}`);
+ }
},
});
diff --git a/src/features/organization/components/edit-organization.dialog.tsx b/src/features/organization/components/edit-organization.dialog.tsx
new file mode 100644
index 00000000..4904faf8
--- /dev/null
+++ b/src/features/organization/components/edit-organization.dialog.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {OrganizationForm} from "@/features/organization/components/organization.form";
+import {useState} from "react";
+import {Button, buttonVariants} from "@/components/ui/button";
+import {GearIcon} from "@radix-ui/react-icons";
+import {OrganizationWithMembers} from "@/db/schema/03_organization";
+import {User} from "@/db/schema/02_user";
+import {User as BetterAuthUser} from "better-auth";
+
+type EditOrganizationDialogProps = {
+ children?: React.ReactNode;
+ organization: OrganizationWithMembers;
+ users: User[];
+ currentUser: BetterAuthUser;
+};
+
+export const EditOrganizationDialog = ({children, organization, users, currentUser}: EditOrganizationDialogProps) => {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ );
+};
diff --git a/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx b/src/features/organization/components/organization.form.tsx
similarity index 91%
rename from src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx
rename to src/features/organization/components/organization.form.tsx
index 8f22800a..872ff70b 100644
--- a/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx
+++ b/src/features/organization/components/organization.form.tsx
@@ -16,13 +16,13 @@ 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";
+ UpdateOrganizationSchema,
+ UpdateOrganizationType
+} from "@/features/organization/organization.schema";
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/03_organization";
import {
updateOrganizationAction
-} from "@/components/wrappers/dashboard/organization/organization.action";
+} from "@/features/organization/organization.action";
import {toast} from "sonner";
import {User as BetterAuthUser} from "better-auth";
import {User} from "@/db/schema/02_user";
@@ -32,6 +32,7 @@ export type organizationFormProps = {
defaultValues?: OrganizationWithMembers;
users: User[];
currentUser: BetterAuthUser;
+ onSuccess?: (data: any) => void;
};
export const OrganizationForm = (props: organizationFormProps) => {
@@ -64,12 +65,12 @@ export const OrganizationForm = (props: organizationFormProps) => {
};
const form = useZodForm({
- schema: OrganizationFormSchema,
+ schema: UpdateOrganizationSchema,
defaultValues: formattedDefaultValues,
});
const mutation = useMutation({
- mutationFn: (values: OrganizationFormType) => updateOrganizationAction({
+ mutationFn: (values: UpdateOrganizationType) => updateOrganizationAction({
data: values,
organizationId: props.defaultValues?.id ?? ""
}),
@@ -78,7 +79,11 @@ export const OrganizationForm = (props: organizationFormProps) => {
toast.success(result.data.actionSuccess?.message || "Organization updated successfully.");
refetch()
refetchActiveOrga()
- router.push("/dashboard/settings");
+ if (props.onSuccess) {
+ props.onSuccess(result.data.value);
+ } else {
+ router.push("/dashboard/settings");
+ }
} else {
// @ts-ignore
const errorMsg = result?.data?.actionError?.message || result?.data?.actionError?.messageParams?.message || "Failed to update the organization.";
diff --git a/src/components/wrappers/dashboard/organization/organization.action.ts b/src/features/organization/organization.action.ts
similarity index 94%
rename from src/components/wrappers/dashboard/organization/organization.action.ts
rename to src/features/organization/organization.action.ts
index 59ef73db..ecccc329 100644
--- a/src/components/wrappers/dashboard/organization/organization.action.ts
+++ b/src/features/organization/organization.action.ts
@@ -1,12 +1,9 @@
"use server";
import {userAction} from "@/lib/safe-actions/actions";
-import {OrganizationSchema} from "@/components/wrappers/dashboard/organization/organization.schema";
+import {CreateOrganizationSchema, UpdateOrganizationSchema} from "@/features/organization/organization.schema";
import {ServerActionResult} from "@/types/action-type";
import {z} from "zod";
-import {
- OrganizationFormSchema
-} from "@/components/wrappers/dashboard/organization/organization-form/organization-form.schema";
import {db} from "@/db";
import {and, eq, inArray, or} from "drizzle-orm";
import {auth, checkSlugOrganization, createOrganization} from "@/lib/auth/auth";
@@ -14,7 +11,7 @@ import {slugify} from "@/utils/slugify";
import {Organization} from "@/db/schema/03_organization";
import * as drizzleDb from "@/db";
-export const createOrganizationAction = userAction.schema(OrganizationSchema).action(async ({parsedInput}): Promise> => {
+export const createOrganizationAction = userAction.schema(CreateOrganizationSchema).action(async ({parsedInput}): Promise> => {
try {
const slug = slugify(parsedInput.name);
if (!await checkSlugOrganization(slug)) {
@@ -69,7 +66,7 @@ export const createOrganizationAction = userAction.schema(OrganizationSchema).ac
export const updateOrganizationAction = userAction
.schema(
z.object({
- data: OrganizationFormSchema,
+ data: UpdateOrganizationSchema,
organizationId: z.string(),
})
)
diff --git a/src/features/organization/organization.schema.ts b/src/features/organization/organization.schema.ts
new file mode 100644
index 00000000..c23c4425
--- /dev/null
+++ b/src/features/organization/organization.schema.ts
@@ -0,0 +1,17 @@
+import { z } from "zod";
+
+export const CreateOrganizationSchema = 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"),
+});
+
+export const UpdateOrganizationSchema = 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'),
+ users: z.array(z.string()),
+});
+
+export type CreateOrganizationType = z.infer;
+export type UpdateOrganizationType = z.infer;
diff --git a/src/features/projects/components/project.dialog.tsx b/src/features/projects/components/project.dialog.tsx
new file mode 100644
index 00000000..7f7ad305
--- /dev/null
+++ b/src/features/projects/components/project.dialog.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {ProjectForm} from "@/features/projects/components/project.form";
+import {useState} from "react";
+import {Button} from "@/components/ui/button";
+import {Plus} from "lucide-react";
+import {DatabaseWith} from "@/db/schema/07_database";
+import {Organization} from "@/db/schema/03_organization";
+import {ProjectWith} from "@/db/schema/06_project";
+
+type ProjectDialogProps = {
+ children?: React.ReactNode;
+ databases: DatabaseWith[];
+ organization: Organization;
+ project?: ProjectWith;
+};
+
+export const ProjectDialog = ({children, databases, organization, project}: ProjectDialogProps) => {
+ const [open, setOpen] = useState(false);
+ const isEdit = !!project;
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/wrappers/dashboard/projects/project-form/project-form.tsx b/src/features/projects/components/project.form.tsx
similarity index 90%
rename from src/components/wrappers/dashboard/projects/project-form/project-form.tsx
rename to src/features/projects/components/project.form.tsx
index 81026bb9..9b41d404 100644
--- a/src/components/wrappers/dashboard/projects/project-form/project-form.tsx
+++ b/src/features/projects/components/project.form.tsx
@@ -1,13 +1,12 @@
"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 { 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 { ProjectSchema, ProjectType } from "@/features/projects/projects.schema";
+import { createProjectAction, updateProjectAction } from "@/features/projects/projects.action";
import { useRouter } from "next/navigation";
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
import { toast } from "sonner";
@@ -19,6 +18,7 @@ export type projectFormProps = {
databases: DatabaseWith[];
organization: Organization;
projectId?: string;
+ onSuccess?: (data: any) => void;
};
export const ProjectForm = (props: projectFormProps) => {
@@ -27,7 +27,7 @@ export const ProjectForm = (props: projectFormProps) => {
const formatDatabasesList = (databases: DatabaseWith[]) => {
return databases.map((database) => ({
value: database.id,
- label: `${database.name} (${database.agentDatabaseId}) | ${database.agent?.name}`,
+ label: `${database.name} | ${database.agent?.name}`,
}));
};
@@ -65,7 +65,11 @@ export const ProjectForm = (props: projectFormProps) => {
if (project.data.success) {
project.data.actionSuccess && toast.success(project.data.actionSuccess.message);
router.refresh();
- router.push(`/dashboard/projects/${project.data.value!.id}`);
+ if (props.onSuccess) {
+ props.onSuccess(project.data.value);
+ } else {
+ router.push(`/dashboard/projects/${project.data.value!.id}`);
+ }
} else {
project.data.actionError && toast.error(project.data.actionError.message || "Unknown error occurred.");
router.refresh();
diff --git a/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts b/src/features/projects/projects.action.ts
similarity index 96%
rename from src/components/wrappers/dashboard/projects/project-form/project-form.action.ts
rename to src/features/projects/projects.action.ts
index af44a47e..24097443 100644
--- a/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts
+++ b/src/features/projects/projects.action.ts
@@ -1,7 +1,7 @@
"use server";
import {userAction} from "@/lib/safe-actions/actions";
-import {ProjectSchema} from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
+import {ProjectSchema} from "@/features/projects/projects.schema";
import {z} from "zod";
import {ServerActionResult} from "@/types/action-type";
import {db} from "@/db";
@@ -119,13 +119,11 @@ export const updateProjectAction = userAction
.where(inArray(drizzleDb.schemas.alertPolicy.databaseId, databasesToRemove)).execute();
}
- // const slug = slugify(parsedInput.data.name);
const [updatedProject] = await db
.update(drizzleDb.schemas.project)
.set({
name: parsedInput.data.name,
- // slug: slug,
})
.where(eq(drizzleDb.schemas.project.id, parsedInput.projectId))
.returning();
diff --git a/src/components/wrappers/dashboard/projects/project-form/project-form.schema.ts b/src/features/projects/projects.schema.ts
similarity index 76%
rename from src/components/wrappers/dashboard/projects/project-form/project-form.schema.ts
rename to src/features/projects/projects.schema.ts
index ac98c212..ef7c0266 100644
--- a/src/components/wrappers/dashboard/projects/project-form/project-form.schema.ts
+++ b/src/features/projects/projects.schema.ts
@@ -1,7 +1,7 @@
import { z } from "zod";
export const ProjectSchema = z.object({
- name: z.string(),
+ name: z.string().nonempty("Name is required"),
databases: z.array(z.string()),
});
From 62a61897889a344e8c75b11fcc66013bc7e4422e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?=
<43389096+Asuniia@users.noreply.github.com>
Date: Wed, 28 Jan 2026 15:54:59 +0100
Subject: [PATCH 2/5] chore(release): 1.2.4-rc.3
---
CITATION.cff | 2 +-
package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/CITATION.cff b/CITATION.cff
index 154995bd..65df8060 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -26,5 +26,5 @@ keywords:
- web-ui
- agent
license: Apache-2.0
-version: 1.2.4-rc.1
+version: 1.2.4-rc.3
date-released: "2026-01-28"
diff --git a/package.json b/package.json
index 7f48ede0..30ffb70f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "portabase",
- "version": "1.2.4-rc.1",
+ "version": "1.2.4-rc.3",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 8887",
From 8f3a362a3a1a999d50a0350161eac255d7169531 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?=
<43389096+Asuniia@users.noreply.github.com>
Date: Wed, 28 Jan 2026 15:55:28 +0100
Subject: [PATCH 3/5] chore(release): 1.2.4-rc.3
---
src/features/projects/components/project.form.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/features/projects/components/project.form.tsx b/src/features/projects/components/project.form.tsx
index 9b41d404..e7b89089 100644
--- a/src/features/projects/components/project.form.tsx
+++ b/src/features/projects/components/project.form.tsx
@@ -21,6 +21,8 @@ export type projectFormProps = {
onSuccess?: (data: any) => void;
};
+
+
export const ProjectForm = (props: projectFormProps) => {
const router = useRouter();
const isCreate = !Boolean(props.defaultValues);
From 2fca928cf0b6be69db8652d27a282eb04cfdc1c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?=
<43389096+Asuniia@users.noreply.github.com>
Date: Wed, 28 Jan 2026 15:55:57 +0100
Subject: [PATCH 4/5] chore(release): 1.2.4-rc.4
---
CITATION.cff | 2 +-
package.json | 2 +-
src/features/projects/components/project.form.tsx | 4 ++++
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CITATION.cff b/CITATION.cff
index 65df8060..3ea5b002 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -26,5 +26,5 @@ keywords:
- web-ui
- agent
license: Apache-2.0
-version: 1.2.4-rc.3
+version: 1.2.4-rc.4
date-released: "2026-01-28"
diff --git a/package.json b/package.json
index 30ffb70f..8851250f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "portabase",
- "version": "1.2.4-rc.3",
+ "version": "1.2.4-rc.4",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 8887",
diff --git a/src/features/projects/components/project.form.tsx b/src/features/projects/components/project.form.tsx
index e7b89089..258c77ee 100644
--- a/src/features/projects/components/project.form.tsx
+++ b/src/features/projects/components/project.form.tsx
@@ -23,6 +23,10 @@ export type projectFormProps = {
+
+
+
+
export const ProjectForm = (props: projectFormProps) => {
const router = useRouter();
const isCreate = !Boolean(props.defaultValues);
From f9bfae9b9d86b3296fe0cc8edbf3b1ec932f4832 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?=
<43389096+Asuniia@users.noreply.github.com>
Date: Wed, 28 Jan 2026 15:56:24 +0100
Subject: [PATCH 5/5] chore(release): 1.2.4-rc.5
---
CITATION.cff | 2 +-
package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/CITATION.cff b/CITATION.cff
index 3ea5b002..52f9d99b 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -26,5 +26,5 @@ keywords:
- web-ui
- agent
license: Apache-2.0
-version: 1.2.4-rc.4
+version: 1.2.4-rc.5
date-released: "2026-01-28"
diff --git a/package.json b/package.json
index 8851250f..1f7d65b9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "portabase",
- "version": "1.2.4-rc.4",
+ "version": "1.2.4-rc.5",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 8887",