diff --git a/app/(customer)/dashboard/agents/[agentId]/page.tsx b/app/(customer)/dashboard/agents/[agentId]/page.tsx index aa0dafac..b0c15850 100644 --- a/app/(customer)/dashboard/agents/[agentId]/page.tsx +++ b/app/(customer)/dashboard/agents/[agentId]/page.tsx @@ -1,5 +1,5 @@ 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 {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; 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 {AgentModalKey} from "@/components/wrappers/Agent/AgentModalKey/AgentModalKey"; import {KeyRound} from "lucide-react"; +import {backups, restaurations} from "@/utils/mock-data"; 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 totalBackups = await prisma.backup.count({ @@ -79,24 +44,24 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>) return ( -
- - {agent.name} - - - - - - - - - - - -
+
+ + {agent.name} + + + + + + + + + + + +
{agent.description}
@@ -135,7 +100,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>) - + diff --git a/app/(customer)/dashboard/page.tsx b/app/(customer)/dashboard/page.tsx index 84ddfb2e..b09ecbad 100644 --- a/app/(customer)/dashboard/page.tsx +++ b/app/(customer)/dashboard/page.tsx @@ -1,14 +1,6 @@ import {PageParams} from "@/types/next"; -import {Button} from "@/components/ui/button"; import {redirect} from "next/navigation"; export default async function RoutePage(props: PageParams<{}>) { redirect("/dashboard/agents") - - // return( - //
- //

Ok

- // - //
- // ) } \ No newline at end of file diff --git a/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx b/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx new file mode 100644 index 00000000..1d02ea5c --- /dev/null +++ b/app/(customer)/dashboard/projects/[projectId]/edit/page.tsx @@ -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 ( + + + + Edit {project.name} + + + + + + + ) +} \ No newline at end of file diff --git a/app/(customer)/dashboard/projects/[projectId]/page.tsx b/app/(customer)/dashboard/projects/[projectId]/page.tsx index 047ea6b8..5fc8b175 100644 --- a/app/(customer)/dashboard/projects/[projectId]/page.tsx +++ b/app/(customer)/dashboard/projects/[projectId]/page.tsx @@ -1,50 +1,42 @@ 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 {AgentCard} from "@/components/wrappers/Agent/AgentCard/AgentCard"; -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 {GearIcon} from "@radix-ui/react-icons"; +import Link from "next/link"; +import {projects} from "@/utils/mock-data"; -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 ( - - - Projects - - {projects.length > 0 && ( - - - - - - )} - - - - - - {projects.length > 0 ? - - : - - Create new Project +
+ + {project.name} + + + + - } - - + +
+ {/*{project.description}*/} +
) diff --git a/app/(customer)/dashboard/projects/new/page.tsx b/app/(customer)/dashboard/projects/new/page.tsx index 81243af5..19f9466e 100644 --- a/app/(customer)/dashboard/projects/new/page.tsx +++ b/app/(customer)/dashboard/projects/new/page.tsx @@ -1,9 +1,20 @@ import {PageParams} from "@/types/next"; 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<{}>) { + const availableDatabases = await prisma.database.findMany({ + where: { + projectId: null + }, + orderBy: { + createdAt: 'desc', + }, + }) + return ( @@ -12,7 +23,12 @@ export default async function RoutePage(props: PageParams<{}>) { - + Add databases + {availableDatabases.map(database => +
+ {database.name} +
)} +
) diff --git a/app/(customer)/dashboard/projects/page.tsx b/app/(customer)/dashboard/projects/page.tsx new file mode 100644 index 00000000..4a5a979a --- /dev/null +++ b/app/(customer)/dashboard/projects/page.tsx @@ -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 ( + + + + Projects + + {projects.length > 0 && ( + + + + + + )} + + + + + + {projects.length > 0 ? + + : + + Create new Project + + + } + + + + + ) +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 731d7cfa..1a07323f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -6,11 +6,15 @@ /* font-family: Arial, Helvetica, sans-serif;*/ /*}*/ -/*@layer utilities {*/ -/* .text-balance {*/ -/* text-wrap: balance;*/ -/* }*/ -/*}*/ +@layer utilities { + /*.text-balance {*/ + /* text-wrap: balance;*/ + /*}*/ + .popover-content-width-full { + width: var(--radix-popover-trigger-width); + max-height: var(--radix-popover-content-available-height); + } +} /*@layer base {*/ /* :root {*/ @@ -86,86 +90,87 @@ @layer base { - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 5.9% 10%; - --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - --sidebar-background: 0 0% 98%; - --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 240 5.9% 10%; - --sidebar-primary-foreground: 0 0% 98%; - --sidebar-accent: 240 4.8% 95.9%; - --sidebar-accent-foreground: 240 5.9% 10%; - --sidebar-border: 220 13% 91%; - --sidebar-ring: 217.2 91.2% 59.8%; - } + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; - } + } } - @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } } + diff --git a/docker-compose.yml b/docker-compose.yml index 2e3f2c78..176a64e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,17 @@ services: - /app/node_modules container_name: portabase-app + develop: + watch: + - action: sync + path: . + target: /app + ignore: + - node_modules/ + - action: rebuild + path: package.json + + db: image: postgres:16-alpine ports: diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 8e1dcfdb..d475fff6 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -6,7 +6,6 @@ import {env} from "@/env.mjs"; import GoogleProvider from "next-auth/providers/google"; - export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ adapter: PrismaAdapter(prisma), theme: { @@ -79,20 +78,22 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ // // session.user.authMethod = user.authMethod; // return session // }, - async jwt({ token, trigger, session, user }) { + async jwt({token, trigger, session, user}) { 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 }) { - session.user = token; + async session({session, token, user}) { + session.user = token.user; + // session.user = token; return session; }, - async signIn({ account, user, profile }) { + async signIn({account, user, profile}) { const existingUser = await prisma.user.findFirst({ - where: { email: user.email }, + where: {email: user.email}, }); if (!existingUser) { @@ -125,7 +126,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ // Update auth method if user exists await prisma.user.update({ - where: { id: existingUser.id }, + where: {id: existingUser.id}, data: { authMethod: account.provider, }, diff --git a/src/auth/current-organization.ts b/src/auth/current-organization.ts new file mode 100644 index 00000000..9478c4d5 --- /dev/null +++ b/src/auth/current-organization.ts @@ -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; +} \ No newline at end of file diff --git a/src/auth/current-user.ts b/src/auth/current-user.ts index c56851db..27ef7a05 100644 --- a/src/auth/current-user.ts +++ b/src/auth/current-user.ts @@ -3,12 +3,14 @@ import {User} from "@prisma/client"; export const currentUser = async () => { const session = await baseAuth(); + console.log("mysession", session) if (!session?.user) { return null; } return session.user as User; } + export const requiredCurrentUser = async () => { const user = await currentUser(); if (!user) { diff --git a/src/components/wrappers/Agent/AgentForm/AgentForm.tsx b/src/components/wrappers/Agent/AgentForm/AgentForm.tsx index a6f25fb5..b99848bb 100644 --- a/src/components/wrappers/Agent/AgentForm/AgentForm.tsx +++ b/src/components/wrappers/Agent/AgentForm/AgentForm.tsx @@ -71,9 +71,9 @@ export const AgentForm = (props: agentFormProps) => { Name + placeholder="Agent 1" {...field} /> - {"Your agent project name"} + Your agent project name )} @@ -89,14 +89,14 @@ export const AgentForm = (props: agentFormProps) => { { const value = e.target.value.replaceAll(" ", "-").toLowerCase() field.onChange(value) }} /> - {'The slug is used in the url of the agent'} + The slug is used in the url of the agent )} @@ -111,10 +111,10 @@ export const AgentForm = (props: agentFormProps) => { Description - {"Enter your project agent description"} + Enter your project agent description )} diff --git a/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx b/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx index 36e15330..0f91d22d 100644 --- a/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx +++ b/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx @@ -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", diff --git a/src/components/wrappers/Dashboard/SideBar/app-sidebar.tsx b/src/components/wrappers/Dashboard/SideBar/app-sidebar.tsx index 8e7de32e..6c4628d7 100644 --- a/src/components/wrappers/Dashboard/SideBar/app-sidebar.tsx +++ b/src/components/wrappers/Dashboard/SideBar/app-sidebar.tsx @@ -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 ( @@ -25,22 +28,10 @@ export function AppSidebar() {
- - - - Select Project - - - - - - Project 1 - - - Project 2 - - - + diff --git a/src/components/wrappers/combobox.tsx b/src/components/wrappers/combobox.tsx index 325f1d2b..cdf64874 100644 --- a/src/components/wrappers/combobox.tsx +++ b/src/components/wrappers/combobox.tsx @@ -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..."} - + - + {searchField ? : null} @@ -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}`} - + - + {searchField ? : null} diff --git a/src/components/wrappers/organization/OrganizationCombobox.tsx b/src/components/wrappers/organization/OrganizationCombobox.tsx new file mode 100644 index 00000000..df1d0928 --- /dev/null +++ b/src/components/wrappers/organization/OrganizationCombobox.tsx @@ -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 ( + + ) +} \ No newline at end of file diff --git a/src/components/wrappers/project/ProjectCard.tsx b/src/components/wrappers/project/ProjectCard.tsx new file mode 100644 index 00000000..3fcbdfa4 --- /dev/null +++ b/src/components/wrappers/project/ProjectCard.tsx @@ -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 ( + + +
+ {project.name} + + +
+
+ + + ) +} \ No newline at end of file diff --git a/src/components/wrappers/project/ProjectForm.action.ts b/src/components/wrappers/project/ProjectForm.action.ts new file mode 100644 index 00000000..d90784da --- /dev/null +++ b/src/components/wrappers/project/ProjectForm.action.ts @@ -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, + } + }); + + }); \ No newline at end of file diff --git a/src/components/wrappers/project/ProjectForm.schema.ts b/src/components/wrappers/project/ProjectForm.schema.ts new file mode 100644 index 00000000..3ee6b62b --- /dev/null +++ b/src/components/wrappers/project/ProjectForm.schema.ts @@ -0,0 +1,9 @@ +import {z} from "zod"; + + +export const ProjectSchema = z.object({ + name: z.string(), + slug: z.string(), +}); + +export type ProjectSchema = z.infer; diff --git a/src/components/wrappers/project/ProjectForm.tsx b/src/components/wrappers/project/ProjectForm.tsx new file mode 100644 index 00000000..79157d37 --- /dev/null +++ b/src/components/wrappers/project/ProjectForm.tsx @@ -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 ( + + + + + +
{ + await mutation.mutateAsync(values); + }} + > + ( + + Name + + + + + + )} + /> + ( + + Slug + + + + + + )} + /> + + +
+
+ ) +} \ No newline at end of file diff --git a/src/safe-actions.ts b/src/safe-actions.ts index c9047df2..f32ddc1b 100644 --- a/src/safe-actions.ts +++ b/src/safe-actions.ts @@ -1,5 +1,8 @@ import {createSafeActionClient} from "next-safe-action"; 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 { constructor(message: string) { @@ -17,15 +20,17 @@ const handleReturnedServerError = (error: Error) => { } + export const action = createSafeActionClient( { handleReturnedServerError: handleReturnedServerError }); -export const userAction = action.use(async ({ next, ctx }) => { +export const userAction = action.use(async ({next, ctx}) => { const user = await currentUser(); + if (!user) { throw new ActionError("You must be logged in"); } - return next({ ctx: { user } }); + return next({ctx: {user}}); }); \ No newline at end of file diff --git a/src/utils/init.ts b/src/utils/init.ts index d95d59a0..bd517ee1 100644 --- a/src/utils/init.ts +++ b/src/utils/init.ts @@ -6,6 +6,10 @@ import {env} from "@/env.mjs"; export function init() { consoleAscii() console.log("====Init Functions====") + + createDefaultOrganization().then(() => { + }) + createSettingsIfNotExist() .then(() => { console.log('====Initialization completed===='); @@ -31,20 +35,19 @@ async function createSettingsIfNotExist() { } - const settings = await prisma.settings.findUnique({ where: { name: "system", } }) - if(!settings){ + if (!settings) { console.log("====Init Setting : Create ====") await prisma.settings.create({ data: { ...configSettings } }) - }else{ + } else { console.log("====Init Setting : Update ====") await prisma.settings.update({ 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" + " ____ __ __ _____ \n" + " / __ \\ ____ _____ / /_ ____ _ / /_ ____ _ _____ ___ / ___/ ___ _____ _ __ ___ _____\n" + diff --git a/src/utils/mock-data.ts b/src/utils/mock-data.ts new file mode 100644 index 00000000..c0ea935f --- /dev/null +++ b/src/utils/mock-data.ts @@ -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'} +] \ No newline at end of file