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:
@@ -1,5 +1,5 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageActions, PageContent, PageDescription, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
||||||
import {Button, buttonVariants} from "@/components/ui/button";
|
import {Button, buttonVariants} from "@/components/ui/button";
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
@@ -11,6 +11,7 @@ import {GearIcon} from "@radix-ui/react-icons";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {AgentModalKey} from "@/components/wrappers/Agent/AgentModalKey/AgentModalKey";
|
import {AgentModalKey} from "@/components/wrappers/Agent/AgentModalKey/AgentModalKey";
|
||||||
import {KeyRound} from "lucide-react";
|
import {KeyRound} from "lucide-react";
|
||||||
|
import {backups, restaurations} from "@/utils/mock-data";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
||||||
@@ -23,42 +24,6 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const backups = [
|
|
||||||
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
|
||||||
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
|
||||||
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
|
||||||
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
|
||||||
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
|
||||||
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
|
||||||
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'},
|
|
||||||
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
|
||||||
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
|
||||||
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
|
||||||
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
|
||||||
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
|
||||||
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
|
||||||
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
|
||||||
{'id': 'backup-11', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
|
||||||
]
|
|
||||||
|
|
||||||
const restores = [
|
|
||||||
{'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
|
||||||
{'id': 'restore-2', 'backupId': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
|
||||||
{'id': 'restore-3', 'backupId': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
|
||||||
{'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
|
||||||
{'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
|
||||||
{'id': 'restore-6', 'backupId': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
|
||||||
{'id': 'restore-7', 'backupId': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
|
||||||
{'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
|
||||||
{'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
|
||||||
{'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
|
||||||
]
|
|
||||||
|
|
||||||
const databaseId = 'db-123';
|
const databaseId = 'db-123';
|
||||||
|
|
||||||
const totalBackups = await prisma.backup.count({
|
const totalBackups = await prisma.backup.count({
|
||||||
@@ -79,24 +44,24 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="justify-between gap-2 sm:flex">
|
<div className="justify-between gap-2 sm:flex">
|
||||||
<PageTitle className="flex items-center">
|
<PageTitle className="flex items-center">
|
||||||
{agent.name}
|
{agent.name}
|
||||||
<Link className={buttonVariants({variant: "outline"})}
|
<Link className={buttonVariants({variant: "outline"})}
|
||||||
href={`/dashboard/agents/${agent.id}/edit`}>
|
href={`/dashboard/agents/${agent.id}/edit`}>
|
||||||
<GearIcon className="w-7 h-7"/>
|
<GearIcon className="w-7 h-7"/>
|
||||||
</Link>
|
</Link>
|
||||||
<AgentModalKey agent={agent}>
|
<AgentModalKey agent={agent}>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
<KeyRound/>
|
<KeyRound/>
|
||||||
</Button>
|
</Button>
|
||||||
</AgentModalKey>
|
</AgentModalKey>
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
<PageActions className="justify-between">
|
<PageActions className="justify-between">
|
||||||
<Button>Backup</Button>
|
<Button>Backup</Button>
|
||||||
<Button>Restore</Button>
|
<Button>Restore</Button>
|
||||||
</PageActions>
|
</PageActions>
|
||||||
</div>
|
</div>
|
||||||
<PageDescription className="mt-5 sm:mt-0">{agent.description}</PageDescription>
|
<PageDescription className="mt-5 sm:mt-0">{agent.description}</PageDescription>
|
||||||
<PageContent className="flex flex-col w-full h-full">
|
<PageContent className="flex flex-col w-full h-full">
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-8 mb-6">
|
<div className="flex flex-col sm:flex-row sm:justify-between gap-8 mb-6">
|
||||||
@@ -135,7 +100,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent className="h-full justify-between" value="restore">
|
<TabsContent className="h-full justify-between" value="restore">
|
||||||
<DataTableWithPagination columns={restoreColumns} data={restores}/>
|
<DataTableWithPagination columns={restoreColumns} data={restaurations}/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Button} from "@/components/ui/button";
|
|
||||||
import {redirect} from "next/navigation";
|
import {redirect} from "next/navigation";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
redirect("/dashboard/agents")
|
redirect("/dashboard/agents")
|
||||||
|
|
||||||
// return(
|
|
||||||
// <div>
|
|
||||||
// <h1>Ok</h1>
|
|
||||||
// <Button>Button</Button>
|
|
||||||
// </div>
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import {PageParams} from "@/types/next";
|
||||||
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
|
import {requiredCurrentUser} from "@/auth/current-user";
|
||||||
|
import {prisma} from "@/prisma";
|
||||||
|
import {notFound} from "next/navigation";
|
||||||
|
import {ProjectForm} from "@/components/wrappers/project/ProjectForm";
|
||||||
|
|
||||||
|
|
||||||
|
export default async function RoutePage(props: PageParams<{
|
||||||
|
projectId: string;
|
||||||
|
}>) {
|
||||||
|
|
||||||
|
const {projectId} = await props.params
|
||||||
|
|
||||||
|
const user = await requiredCurrentUser()
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: {
|
||||||
|
id: projectId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<PageHeader>
|
||||||
|
<PageTitle>
|
||||||
|
Edit {project.name}
|
||||||
|
</PageTitle>
|
||||||
|
</PageHeader>
|
||||||
|
<PageContent>
|
||||||
|
<ProjectForm defaultValues={project} projectId={project.id}/>
|
||||||
|
</PageContent>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,50 +1,42 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
|
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
||||||
|
import {buttonVariants} from "@/components/ui/button";
|
||||||
|
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
import {AgentCard} from "@/components/wrappers/Agent/AgentCard/AgentCard";
|
import {GearIcon} from "@radix-ui/react-icons";
|
||||||
import {CardsWithPagination} from "@/components/wrappers/cards-with-pagination";
|
import Link from "next/link";
|
||||||
import {Button} from "@/components/ui/button";
|
import {projects} from "@/utils/mock-data";
|
||||||
import Link from 'next/link'
|
|
||||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
|
||||||
|
|
||||||
const projects = await prisma.project.findMany({})
|
export default async function RoutePage(props: PageParams<{ projectId: string }>) {
|
||||||
|
|
||||||
|
const {projectId} = await props.params
|
||||||
|
|
||||||
|
// const project = await prisma.project.findUnique({
|
||||||
|
// where: {
|
||||||
|
// id: projectId,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
const project = projects.find(p => p.id === projectId)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<PageHeader>
|
<div className="justify-between gap-2 sm:flex">
|
||||||
<PageTitle>
|
<PageTitle className="flex items-center">
|
||||||
Projects
|
{project.name}
|
||||||
</PageTitle>
|
<Link className={buttonVariants({variant: "outline"})}
|
||||||
{projects.length > 0 && (
|
href={`/dashboard/projects/${project.id}/edit`}>
|
||||||
<PageActions>
|
<GearIcon className="w-7 h-7"/>
|
||||||
<Link href={"/dashboard/projects/new"}>
|
|
||||||
<Button>+ Create Project</Button>
|
|
||||||
</Link>
|
|
||||||
</PageActions>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<PageContent className="mt-10">
|
|
||||||
|
|
||||||
{projects.length > 0 ?
|
|
||||||
<CardsWithPagination
|
|
||||||
data={projects}
|
|
||||||
cardItem={AgentCard}
|
|
||||||
cardsPerPage={4}
|
|
||||||
numberOfColumns={1}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
<Link
|
|
||||||
href="/dashboard/projects/new"
|
|
||||||
className=" flex item-center justify-center border-2 border-dashed transition-colors border-primary p-8 lg:p-12 w-full rounded-md">
|
|
||||||
Create new Project
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</PageTitle>
|
||||||
|
<PageActions className="justify-between">
|
||||||
|
|
||||||
}
|
</PageActions>
|
||||||
|
</div>
|
||||||
|
{/*<PageDescription className="mt-5 sm:mt-0">{project.description}</PageDescription>*/}
|
||||||
|
<PageContent className="flex flex-col w-full h-full">
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
|
import {prisma} from "@/prisma";
|
||||||
|
import {ProjectForm} from "@/components/wrappers/project/ProjectForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
|
const availableDatabases = await prisma.database.findMany({
|
||||||
|
where: {
|
||||||
|
projectId: null
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
@@ -12,7 +23,12 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
|
Add databases
|
||||||
|
{availableDatabases.map(database =>
|
||||||
|
<div key={database.id}>
|
||||||
|
{database.name}
|
||||||
|
</div>)}
|
||||||
|
<ProjectForm/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {PageParams} from "@/types/next";
|
||||||
|
import {prisma} from "@/prisma";
|
||||||
|
import {CardsWithPagination} from "@/components/wrappers/cards-with-pagination";
|
||||||
|
import {Button} from "@/components/ui/button";
|
||||||
|
import Link from 'next/link'
|
||||||
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
|
import {projects} from "@/utils/mock-data";
|
||||||
|
import {ProjectCard} from "@/components/wrappers/project/ProjectCard";
|
||||||
|
|
||||||
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
|
// const projects = await prisma.project.findMany({})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<PageHeader>
|
||||||
|
<PageTitle>
|
||||||
|
Projects
|
||||||
|
</PageTitle>
|
||||||
|
{projects.length > 0 && (
|
||||||
|
<PageActions>
|
||||||
|
<Link href={"/dashboard/projects/new"}>
|
||||||
|
<Button>+ Create Project</Button>
|
||||||
|
</Link>
|
||||||
|
</PageActions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<PageContent className="mt-10">
|
||||||
|
|
||||||
|
{projects.length > 0 ?
|
||||||
|
<CardsWithPagination
|
||||||
|
data={projects}
|
||||||
|
cardItem={ProjectCard}
|
||||||
|
cardsPerPage={4}
|
||||||
|
numberOfColumns={1}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<Link
|
||||||
|
href="/dashboard/projects/new"
|
||||||
|
className=" flex item-center justify-center border-2 border-dashed transition-colors border-primary p-8 lg:p-12 w-full rounded-md">
|
||||||
|
Create new Project
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</PageContent>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
+86
-81
@@ -6,11 +6,15 @@
|
|||||||
/* font-family: Arial, Helvetica, sans-serif;*/
|
/* font-family: Arial, Helvetica, sans-serif;*/
|
||||||
/*}*/
|
/*}*/
|
||||||
|
|
||||||
/*@layer utilities {*/
|
@layer utilities {
|
||||||
/* .text-balance {*/
|
/*.text-balance {*/
|
||||||
/* text-wrap: balance;*/
|
/* text-wrap: balance;*/
|
||||||
/* }*/
|
/*}*/
|
||||||
/*}*/
|
.popover-content-width-full {
|
||||||
|
width: var(--radix-popover-trigger-width);
|
||||||
|
max-height: var(--radix-popover-content-available-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*@layer base {*/
|
/*@layer base {*/
|
||||||
/* :root {*/
|
/* :root {*/
|
||||||
@@ -86,86 +90,87 @@
|
|||||||
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 240 10% 3.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 240 10% 3.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 240 10% 3.9%;
|
||||||
--primary: 240 5.9% 10%;
|
--primary: 240 5.9% 10%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 240 4.8% 95.9%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 240 4.8% 95.9%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 240 4.8% 95.9%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 240 5.9% 10%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 240 5.9% 90%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 240 5.9% 90%;
|
||||||
--ring: 240 5.9% 10%;
|
--ring: 240 5.9% 10%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--chart-1: 12 76% 61%;
|
--chart-1: 12 76% 61%;
|
||||||
--chart-2: 173 58% 39%;
|
--chart-2: 173 58% 39%;
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
--sidebar-background: 0 0% 98%;
|
--sidebar-background: 0 0% 98%;
|
||||||
--sidebar-foreground: 240 5.3% 26.1%;
|
--sidebar-foreground: 240 5.3% 26.1%;
|
||||||
--sidebar-primary: 240 5.9% 10%;
|
--sidebar-primary: 240 5.9% 10%;
|
||||||
--sidebar-primary-foreground: 0 0% 98%;
|
--sidebar-primary-foreground: 0 0% 98%;
|
||||||
--sidebar-accent: 240 4.8% 95.9%;
|
--sidebar-accent: 240 4.8% 95.9%;
|
||||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||||
--sidebar-border: 220 13% 91%;
|
--sidebar-border: 220 13% 91%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 240 10% 3.9%;
|
--background: 240 10% 3.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 240 10% 3.9%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 0 0% 98%;
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 240 10% 3.9%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 0 0% 98%;
|
||||||
--primary: 0 0% 98%;
|
--primary: 0 0% 98%;
|
||||||
--primary-foreground: 240 5.9% 10%;
|
--primary-foreground: 240 5.9% 10%;
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 240 3.7% 15.9%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 0 0% 98%;
|
||||||
--muted: 240 3.7% 15.9%;
|
--muted: 240 3.7% 15.9%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 240 5% 64.9%;
|
||||||
--accent: 240 3.7% 15.9%;
|
--accent: 240 3.7% 15.9%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 240 3.7% 15.9%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 240 4.9% 83.9%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
--sidebar-background: 240 5.9% 10%;
|
--sidebar-background: 240 5.9% 10%;
|
||||||
--sidebar-foreground: 240 4.8% 95.9%;
|
--sidebar-foreground: 240 4.8% 95.9%;
|
||||||
--sidebar-primary: 224.3 76.3% 48%;
|
--sidebar-primary: 224.3 76.3% 48%;
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: 0 0% 100%;
|
||||||
--sidebar-accent: 240 3.7% 15.9%;
|
--sidebar-accent: 240 3.7% 15.9%;
|
||||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||||
--sidebar-border: 240 3.7% 15.9%;
|
--sidebar-border: 240 3.7% 15.9%;
|
||||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
body {
|
||||||
}
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ services:
|
|||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
container_name: portabase-app
|
container_name: portabase-app
|
||||||
|
|
||||||
|
develop:
|
||||||
|
watch:
|
||||||
|
- action: sync
|
||||||
|
path: .
|
||||||
|
target: /app
|
||||||
|
ignore:
|
||||||
|
- node_modules/
|
||||||
|
- action: rebuild
|
||||||
|
path: package.json
|
||||||
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
+10
-9
@@ -6,7 +6,6 @@ import {env} from "@/env.mjs";
|
|||||||
import GoogleProvider from "next-auth/providers/google";
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
theme: {
|
theme: {
|
||||||
@@ -79,20 +78,22 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
// // session.user.authMethod = user.authMethod;
|
// // session.user.authMethod = user.authMethod;
|
||||||
// return session
|
// return session
|
||||||
// },
|
// },
|
||||||
async jwt({ token, trigger, session, user }) {
|
async jwt({token, trigger, session, user}) {
|
||||||
if (trigger === "update" && session) {
|
if (trigger === "update" && session) {
|
||||||
return { ...token, ...session?.user };
|
return {...session, user: {...token, ...session?.user}};
|
||||||
|
// return {...token, ...session?.user};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...token, ...user };
|
return {...token, ...user};
|
||||||
},
|
},
|
||||||
async session({ session, token, user }) {
|
async session({session, token, user}) {
|
||||||
session.user = token;
|
session.user = token.user;
|
||||||
|
// session.user = token;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
async signIn({ account, user, profile }) {
|
async signIn({account, user, profile}) {
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: { email: user.email },
|
where: {email: user.email},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
@@ -125,7 +126,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
|
|
||||||
// Update auth method if user exists
|
// Update auth method if user exists
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: existingUser.id },
|
where: {id: existingUser.id},
|
||||||
data: {
|
data: {
|
||||||
authMethod: account.provider,
|
authMethod: account.provider,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import {baseAuth} from "@/auth/auth";
|
||||||
|
import {Organization} from "@prisma/client";
|
||||||
|
|
||||||
|
export const currentOrganization = async () => {
|
||||||
|
const session = await baseAuth();
|
||||||
|
console.log("mysession", session)
|
||||||
|
if (!session?.organization) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return session.organization as Organization;
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ import {User} from "@prisma/client";
|
|||||||
|
|
||||||
export const currentUser = async () => {
|
export const currentUser = async () => {
|
||||||
const session = await baseAuth();
|
const session = await baseAuth();
|
||||||
|
console.log("mysession", session)
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return session.user as User;
|
return session.user as User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const requiredCurrentUser = async () => {
|
export const requiredCurrentUser = async () => {
|
||||||
const user = await currentUser();
|
const user = await currentUser();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ export const AgentForm = (props: agentFormProps) => {
|
|||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={"Project 1"} {...field} />
|
placeholder="Agent 1" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>{"Your agent project name"}</FormDescription>
|
<FormDescription>Your agent project name</FormDescription>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -89,14 +89,14 @@ export const AgentForm = (props: agentFormProps) => {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
placeholder={"agent-5-project-1"} {...field}
|
placeholder="agent-1" {...field}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value.replaceAll(" ", "-").toLowerCase()
|
const value = e.target.value.replaceAll(" ", "-").toLowerCase()
|
||||||
field.onChange(value)
|
field.onChange(value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</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/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -111,10 +111,10 @@ export const AgentForm = (props: agentFormProps) => {
|
|||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={'This agent is for the client exemple.com'} {...field}
|
placeholder='This agent is for the client exemple.com' {...field}
|
||||||
value={field.value ?? ""}/>
|
value={field.value ?? ""}/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>{"Enter your project agent description"}</FormDescription>
|
<FormDescription>Enter your project agent description</FormDescription>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import {SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem} from "@/components/ui/sidebar";
|
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import {SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem} from "@/components/ui/sidebar";
|
||||||
import {cn} from "@/lib/utils";
|
import {cn} from "@/lib/utils";
|
||||||
import {buttonVariants} from "@/components/ui/button";
|
import {buttonVariants} from "@/components/ui/button";
|
||||||
import {PropsWithChildren, useEffect, useState} from "react";
|
import {ChartArea, FolderKanban, Home, Settings, ShieldHalf} from "lucide-react";
|
||||||
import {ChartArea, Home, Settings, ShieldHalf} from "lucide-react";
|
|
||||||
import {usePathname} from "next/navigation";
|
import {usePathname} from "next/navigation";
|
||||||
|
|
||||||
export type SidebarMenuCustomProps = {}
|
export type SidebarMenuCustomProps = {}
|
||||||
@@ -20,6 +21,11 @@ export const SidebarMenuCustom = (props: SidebarMenuCustomProps) => {
|
|||||||
// url: "/",
|
// url: "/",
|
||||||
// icon: Home,
|
// icon: Home,
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
title: "Projects",
|
||||||
|
url: "projects",
|
||||||
|
icon: FolderKanban,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Agents",
|
title: "Agents",
|
||||||
url: "agents",
|
url: "agents",
|
||||||
|
|||||||
@@ -4,18 +4,21 @@ import {
|
|||||||
SidebarGroupLabel,
|
SidebarGroupLabel,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar"
|
} 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 {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
|
||||||
import {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
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 (
|
return (
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
@@ -25,22 +28,10 @@ export function AppSidebar() {
|
|||||||
</div>
|
</div>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<DropdownMenu>
|
<OrganizationComboBox
|
||||||
<DropdownMenuTrigger asChild>
|
organizations={organizations}
|
||||||
<SidebarMenuButton>
|
defaultOrganization={defaultOrganization}
|
||||||
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>
|
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"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 {cn} from "@/lib/utils"
|
||||||
import {Button} from "@/components/ui/button"
|
import {Button} from "@/components/ui/button"
|
||||||
@@ -45,15 +45,15 @@ export function ComboBox(props: comboBoxProps) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className="w-[200px] justify-between"
|
className="w-full justify-between"
|
||||||
>
|
>
|
||||||
{value
|
{value
|
||||||
? choices.find((choice) => choice.value === value)?.label
|
? choices.find((choice) => choice.value === value)?.label
|
||||||
: "Select choice..."}
|
: "Select choice..."}
|
||||||
<ChevronsUpDown className="opacity-50"/>
|
<ChevronDown className="opacity-50"/>
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[200px] p-0">
|
<PopoverContent className="p-0 popover-content-width-full">
|
||||||
<Command>
|
<Command>
|
||||||
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
||||||
<CommandList>
|
<CommandList>
|
||||||
@@ -64,8 +64,8 @@ export function ComboBox(props: comboBoxProps) {
|
|||||||
key={choice.value}
|
key={choice.value}
|
||||||
value={choice.value}
|
value={choice.value}
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
setValue(currentValue === value ? "" : currentValue)
|
setValue(currentValue)
|
||||||
onValueChange(currentValue === value ? "" : currentValue)
|
onValueChange(currentValue)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -93,6 +93,7 @@ export type comboBoxFormItemProps = comboBoxProps & {
|
|||||||
onChange: any
|
onChange: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Combobox to use when working with zodForm. */
|
||||||
export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
||||||
|
|
||||||
const {values: choices, searchField = false, value, name, onChange} = props;
|
const {values: choices, searchField = false, value, name, onChange} = props;
|
||||||
@@ -108,7 +109,7 @@ export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
|||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-[200px] justify-between",
|
"w-full justify-between",
|
||||||
!value && "text-muted-foreground"
|
!value && "text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -117,11 +118,11 @@ export function ComboBoxFormItem(props: comboBoxFormItemProps) {
|
|||||||
(choice) => choice.value === value
|
(choice) => choice.value === value
|
||||||
)?.label
|
)?.label
|
||||||
: `Select ${name}`}
|
: `Select ${name}`}
|
||||||
<ChevronsUpDown className="opacity-50"/>
|
<ChevronDown className="opacity-50"/>
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[200px] p-0">
|
<PopoverContent className="p-0 popover-content-width-full">
|
||||||
<Command>
|
<Command>
|
||||||
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
||||||
<CommandList>
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+7
-2
@@ -1,5 +1,8 @@
|
|||||||
import {createSafeActionClient} from "next-safe-action";
|
import {createSafeActionClient} from "next-safe-action";
|
||||||
import {currentUser} from "@/auth/current-user";
|
import {currentUser} from "@/auth/current-user";
|
||||||
|
import {baseAuth} from "@/auth/auth";
|
||||||
|
import {Organization} from "@prisma/client";
|
||||||
|
import {currentOrganization} from "@/auth/current-organization";
|
||||||
|
|
||||||
export class ActionError extends Error {
|
export class ActionError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
@@ -17,15 +20,17 @@ const handleReturnedServerError = (error: Error) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const action = createSafeActionClient(
|
export const action = createSafeActionClient(
|
||||||
{
|
{
|
||||||
handleReturnedServerError: handleReturnedServerError
|
handleReturnedServerError: handleReturnedServerError
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userAction = action.use(async ({ next, ctx }) => {
|
export const userAction = action.use(async ({next, ctx}) => {
|
||||||
const user = await currentUser();
|
const user = await currentUser();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ActionError("You must be logged in");
|
throw new ActionError("You must be logged in");
|
||||||
}
|
}
|
||||||
return next({ ctx: { user } });
|
return next({ctx: {user}});
|
||||||
});
|
});
|
||||||
+29
-4
@@ -6,6 +6,10 @@ import {env} from "@/env.mjs";
|
|||||||
export function init() {
|
export function init() {
|
||||||
consoleAscii()
|
consoleAscii()
|
||||||
console.log("====Init Functions====")
|
console.log("====Init Functions====")
|
||||||
|
|
||||||
|
createDefaultOrganization().then(() => {
|
||||||
|
})
|
||||||
|
|
||||||
createSettingsIfNotExist()
|
createSettingsIfNotExist()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('====Initialization completed====');
|
console.log('====Initialization completed====');
|
||||||
@@ -31,20 +35,19 @@ async function createSettingsIfNotExist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const settings = await prisma.settings.findUnique({
|
const settings = await prisma.settings.findUnique({
|
||||||
where: {
|
where: {
|
||||||
name: "system",
|
name: "system",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(!settings){
|
if (!settings) {
|
||||||
console.log("====Init Setting : Create ====")
|
console.log("====Init Setting : Create ====")
|
||||||
await prisma.settings.create({
|
await prisma.settings.create({
|
||||||
data: {
|
data: {
|
||||||
...configSettings
|
...configSettings
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}else{
|
} else {
|
||||||
console.log("====Init Setting : Update ====")
|
console.log("====Init Setting : Update ====")
|
||||||
await prisma.settings.update({
|
await prisma.settings.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -58,7 +61,29 @@ async function createSettingsIfNotExist() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function consoleAscii(){
|
async function createDefaultOrganization() {
|
||||||
|
|
||||||
|
const defaultOrganizationConf = {
|
||||||
|
slug: "default",
|
||||||
|
name: "Default Organization",
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOrganization = await prisma.organization.findUnique({
|
||||||
|
where: {
|
||||||
|
slug: "default",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!defaultOrganization) {
|
||||||
|
console.log("==== Creating default organization... ====\n")
|
||||||
|
await prisma.organization.create({
|
||||||
|
data: {
|
||||||
|
...defaultOrganizationConf
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function consoleAscii() {
|
||||||
console.log("\n" +
|
console.log("\n" +
|
||||||
" ____ __ __ _____ \n" +
|
" ____ __ __ _____ \n" +
|
||||||
" / __ \\ ____ _____ / /_ ____ _ / /_ ____ _ _____ ___ / ___/ ___ _____ _ __ ___ _____\n" +
|
" / __ \\ ____ _____ / /_ ____ _ / /_ ____ _ _____ ___ / ___/ ___ _____ _ __ ___ _____\n" +
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
export const organizations = [
|
||||||
|
{
|
||||||
|
"id": "org-1a2b3c4e",
|
||||||
|
"slug": "default",
|
||||||
|
"name": "Default Organization",
|
||||||
|
"createdAt": "2024-01-10T10:00:00.000Z",
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org-1a2b3c4d",
|
||||||
|
"slug": "tech-corp",
|
||||||
|
"name": "Tech Corp",
|
||||||
|
"createdAt": "2024-01-10T10:00:00.000Z",
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org-2e3f4g5h",
|
||||||
|
"slug": "design-studio",
|
||||||
|
"name": "Design Studio",
|
||||||
|
"createdAt": "2023-09-15T15:45:00.000Z",
|
||||||
|
"projects": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const projects = [
|
||||||
|
{
|
||||||
|
"id": "proj-1234abcd",
|
||||||
|
"slug": "backend-system",
|
||||||
|
"name": "Backend System",
|
||||||
|
"createdAt": "2024-02-20T11:30:00.000Z",
|
||||||
|
"organizationId": "org-1a2b3c4d",
|
||||||
|
"databases": []
|
||||||
|
}, {
|
||||||
|
"id": "proj-5678efgh",
|
||||||
|
"slug": "creative-suite",
|
||||||
|
"name": "Creative Suite",
|
||||||
|
"createdAt": "2023-10-10T08:20:00.000Z",
|
||||||
|
"organizationId": "org-2e3f4g5h",
|
||||||
|
"databases": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const databases = [
|
||||||
|
{
|
||||||
|
"id": "c1a8e77c-eed9-4c8f-a9f7-90deabc3b78e",
|
||||||
|
"name": "Main Production DB",
|
||||||
|
"dbms": "postgresql",
|
||||||
|
"generatedId": "prod-db-1",
|
||||||
|
"description": "Primary database for production environment",
|
||||||
|
"backupPolicy": "daily",
|
||||||
|
"createdAt": "2024-11-01T12:00:00.000Z",
|
||||||
|
"agentId": "agent-1234",
|
||||||
|
"lastContact": "2024-11-28T08:30:00.000Z",
|
||||||
|
"projectId": "proj-789",
|
||||||
|
"backups": [],
|
||||||
|
"restaurations": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1e4f9265-5f10-4bd4-8e5c-3283746cfd7a",
|
||||||
|
"name": "Staging DB",
|
||||||
|
"dbms": "mysql",
|
||||||
|
"generatedId": "staging-db-2",
|
||||||
|
"description": "Database for testing and staging environment",
|
||||||
|
"backupPolicy": "weekly",
|
||||||
|
"createdAt": "2024-10-15T09:45:00.000Z",
|
||||||
|
"agentId": "agent-5678",
|
||||||
|
"lastContact": "2024-11-25T10:15:00.000Z",
|
||||||
|
"projectId": null,
|
||||||
|
"backups": [],
|
||||||
|
"restaurations": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b6f92bd7-834b-41c1-b6b3-dc4214b1de08",
|
||||||
|
"name": "Development DB",
|
||||||
|
"dbms": "mongodb",
|
||||||
|
"generatedId": "dev-db-3",
|
||||||
|
"description": null,
|
||||||
|
"backupPolicy": null,
|
||||||
|
"createdAt": "2024-09-20T16:20:00.000Z",
|
||||||
|
"agentId": "agent-9012",
|
||||||
|
"lastContact": null,
|
||||||
|
"projectId": "proj-456",
|
||||||
|
"backups": [],
|
||||||
|
"restaurations": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const backups = [
|
||||||
|
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-11', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const restaurations = [
|
||||||
|
{'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
|
{'id': 'restore-2', 'backupId': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
|
{'id': 'restore-3', 'backupId': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
|
{'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
|
{'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
|
{'id': 'restore-6', 'backupId': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
|
{'id': 'restore-7', 'backupId': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
|
{'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
|
{'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
|
{'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user