mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
adding project features.
This commit is contained in:
@@ -71,9 +71,9 @@ export const AgentForm = (props: agentFormProps) => {
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Project 1"} {...field} />
|
||||
placeholder="Agent 1" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Your agent project name"}</FormDescription>
|
||||
<FormDescription>Your agent project name</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -89,14 +89,14 @@ export const AgentForm = (props: agentFormProps) => {
|
||||
<FormControl>
|
||||
<Input
|
||||
value={field.value ?? ""}
|
||||
placeholder={"agent-5-project-1"} {...field}
|
||||
placeholder="agent-1" {...field}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replaceAll(" ", "-").toLowerCase()
|
||||
field.onChange(value)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>{'The slug is used in the url of the agent'}</FormDescription>
|
||||
<FormDescription>The slug is used in the url of the agent</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -111,10 +111,10 @@ export const AgentForm = (props: agentFormProps) => {
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={'This agent is for the client exemple.com'} {...field}
|
||||
placeholder='This agent is for the client exemple.com' {...field}
|
||||
value={field.value ?? ""}/>
|
||||
</FormControl>
|
||||
<FormDescription>{"Enter your project agent description"}</FormDescription>
|
||||
<FormDescription>Enter your project agent description</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client"
|
||||
import {SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem} from "@/components/ui/sidebar";
|
||||
|
||||
import {useEffect, useState} from "react";
|
||||
import Link from "next/link";
|
||||
import {SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem} from "@/components/ui/sidebar";
|
||||
import {cn} from "@/lib/utils";
|
||||
import {buttonVariants} from "@/components/ui/button";
|
||||
import {PropsWithChildren, useEffect, useState} from "react";
|
||||
import {ChartArea, Home, Settings, ShieldHalf} from "lucide-react";
|
||||
import {ChartArea, FolderKanban, Home, Settings, ShieldHalf} from "lucide-react";
|
||||
import {usePathname} from "next/navigation";
|
||||
|
||||
export type SidebarMenuCustomProps = {}
|
||||
@@ -20,6 +21,11 @@ export const SidebarMenuCustom = (props: SidebarMenuCustomProps) => {
|
||||
// url: "/",
|
||||
// icon: Home,
|
||||
// },
|
||||
{
|
||||
title: "Projects",
|
||||
url: "projects",
|
||||
icon: FolderKanban,
|
||||
},
|
||||
{
|
||||
title: "Agents",
|
||||
url: "agents",
|
||||
|
||||
@@ -4,18 +4,21 @@ import {
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
ChevronDown
|
||||
} from "lucide-react";
|
||||
import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
|
||||
import {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
||||
import Image from 'next/image';
|
||||
import {OrganizationComboBox} from "@/components/wrappers/organization/OrganizationCombobox";
|
||||
import {prisma} from "@/prisma";
|
||||
|
||||
export function AppSidebar() {
|
||||
export async function AppSidebar() {
|
||||
|
||||
const organizations = await prisma.organization.findMany({})
|
||||
const defaultOrganization = await prisma.organization.findUnique({
|
||||
where: {
|
||||
slug: "default"
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
@@ -25,22 +28,10 @@ export function AppSidebar() {
|
||||
</div>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton>
|
||||
Select Project
|
||||
<ChevronDown className="ml-auto"/>
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[--radix-popper-anchor-width]">
|
||||
<DropdownMenuItem>
|
||||
<span>Project 1</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<span>Project 2</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<OrganizationComboBox
|
||||
organizations={organizations}
|
||||
defaultOrganization={defaultOrganization}
|
||||
/>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import {useEffect, useState} from "react";
|
||||
import {useState} from "react";
|
||||
|
||||
import {Check, ChevronsUpDown} from "lucide-react"
|
||||
import {Check, ChevronDown} from "lucide-react"
|
||||
|
||||
import {cn} from "@/lib/utils"
|
||||
import {Button} from "@/components/ui/button"
|
||||
@@ -45,15 +45,15 @@ export function ComboBox(props: comboBoxProps) {
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-[200px] justify-between"
|
||||
className="w-full justify-between"
|
||||
>
|
||||
{value
|
||||
? choices.find((choice) => choice.value === value)?.label
|
||||
: "Select choice..."}
|
||||
<ChevronsUpDown className="opacity-50"/>
|
||||
<ChevronDown className="opacity-50"/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<PopoverContent className="p-0 popover-content-width-full">
|
||||
<Command>
|
||||
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
||||
<CommandList>
|
||||
@@ -64,8 +64,8 @@ export function ComboBox(props: comboBoxProps) {
|
||||
key={choice.value}
|
||||
value={choice.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
onValueChange(currentValue === value ? "" : currentValue)
|
||||
setValue(currentValue)
|
||||
onValueChange(currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
@@ -93,6 +93,7 @@ export type comboBoxFormItemProps = comboBoxProps & {
|
||||
onChange: any
|
||||
};
|
||||
|
||||
/** Combobox to use when working with zodForm. */
|
||||
export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
||||
|
||||
const {values: choices, searchField = false, value, name, onChange} = props;
|
||||
@@ -108,7 +109,7 @@ export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"w-[200px] justify-between",
|
||||
"w-full justify-between",
|
||||
!value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
@@ -117,11 +118,11 @@ export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
||||
(choice) => choice.value === value
|
||||
)?.label
|
||||
: `Select ${name}`}
|
||||
<ChevronsUpDown className="opacity-50"/>
|
||||
<ChevronDown className="opacity-50"/>
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<PopoverContent className="p-0 popover-content-width-full">
|
||||
<Command>
|
||||
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
||||
<CommandList>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"use client"
|
||||
|
||||
import {useSession} from "next-auth/react";
|
||||
|
||||
import {ComboBox} from "@/components/wrappers/combobox";
|
||||
import {Organization} from "@prisma/client";
|
||||
|
||||
export type organizationComboBoxProps = {
|
||||
organizations: Organization[]
|
||||
defaultOrganization: Organization
|
||||
}
|
||||
|
||||
|
||||
export function OrganizationComboBox(props: organizationComboBoxProps) {
|
||||
|
||||
const {organizations, defaultOrganization} = props
|
||||
|
||||
const values = organizations.map(organization => {
|
||||
return ({
|
||||
value: organization.id,
|
||||
label: organization.name,
|
||||
})
|
||||
})
|
||||
|
||||
const {data: session, update} = useSession();
|
||||
|
||||
const updateSession = async (organizationId: string) => {
|
||||
const organization = organizations.find(organization => organization.id === organizationId)
|
||||
await update({...session, organization: organization})
|
||||
console.log("session updtated", organization)
|
||||
}
|
||||
|
||||
return (
|
||||
<ComboBox values={values} defaultValue={defaultOrganization.id} onValueChange={updateSession}/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import Link from "next/link";
|
||||
|
||||
export type projectCardProps = {
|
||||
data: any
|
||||
}
|
||||
|
||||
export const ProjectCard = (props: projectCardProps) => {
|
||||
|
||||
const {data: project} = props;
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/projects/${project.id}`}>
|
||||
<Card className="flex flex-row justify-between">
|
||||
<div className="">
|
||||
<CardHeader>{project.name}</CardHeader>
|
||||
<CardContent>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
"use server"
|
||||
|
||||
import {userAction} from "@/safe-actions";
|
||||
import {prisma} from "@/prisma";
|
||||
import {ProjectSchema} from "@/components/wrappers/project/ProjectForm.schema";
|
||||
|
||||
|
||||
export const createProjectAction = userAction
|
||||
.schema(ProjectSchema)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
// Verify if slug already exist
|
||||
// await verifySlugUniqueness(parsedInput.slug);
|
||||
|
||||
console.log("ctx", ctx)
|
||||
return prisma.project.create({
|
||||
data: {
|
||||
...parsedInput,
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import {z} from "zod";
|
||||
|
||||
|
||||
export const ProjectSchema = z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
});
|
||||
|
||||
export type ProjectSchema = z.infer<typeof ProjectSchema>;
|
||||
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {FormControl, 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} from "@/components/wrappers/project/ProjectForm.schema";
|
||||
import {createProjectAction} from "@/components/wrappers/project/ProjectForm.action";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useSession} from "next-auth/react";
|
||||
|
||||
export type projectFormProps = {
|
||||
defaultValues?: ProjectSchema;
|
||||
}
|
||||
|
||||
export const ProjectForm = (props: projectFormProps) => {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const {data: session} = useSession()
|
||||
console.log("organization", session)
|
||||
|
||||
const form = useZodForm({
|
||||
schema: ProjectSchema,
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: ProjectSchema) => {
|
||||
|
||||
const {data, validationErrors} = await createProjectAction({...values, organizationId: organization?.id});
|
||||
|
||||
if (data) {
|
||||
toast.success(`Success`);
|
||||
router.push(`/dashboard/projects/${data.id}`);
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
if (validationErrors) {
|
||||
toast.success(`Error`);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form form={form}
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={async (values) => {
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Project 1" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="slug"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Slug</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="project-1" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button>
|
||||
Create Project
|
||||
</Button>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user